Merge "init: add unit tests for tokenizer.cpp"
diff --git a/adb/Android.bp b/adb/Android.bp
index 97c9762..c0e4c57 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -19,17 +19,13 @@
         "-Wall",
         "-Wextra",
         "-Werror",
+        "-Wexit-time-destructors",
         "-Wno-unused-parameter",
         "-Wno-missing-field-initializers",
         "-Wvla",
     ],
     rtti: true,
 
-    clang_cflags: [
-        "-Wexit-time-destructors",
-        "-Wthread-safety",
-    ],
-
     use_version_lib: true,
 
     compile_multilib: "first",
@@ -85,6 +81,12 @@
                 "-luserenv",
             ],
         },
+
+        not_windows: {
+            cflags: [
+                "-Wthread-safety",
+            ],
+        },
     },
 }
 
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/adb.h b/adb/adb.h
index ede55da..c884166 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -124,8 +124,6 @@
 
 void print_packet(const char* label, apacket* p);
 
-// These use the system (v)fprintf, not the adb prefixed ones defined in sysdeps.h, so they
-// shouldn't be tagged with ADB_FORMAT_ARCHETYPE.
 void fatal(const char* fmt, ...) __attribute__((noreturn, format(__printf__, 1, 2)));
 void fatal_errno(const char* fmt, ...) __attribute__((noreturn, format(__printf__, 1, 2)));
 
diff --git a/adb/client/file_sync_client.cpp b/adb/client/file_sync_client.cpp
index 26f8d83..1275641 100644
--- a/adb/client/file_sync_client.cpp
+++ b/adb/client/file_sync_client.cpp
@@ -510,8 +510,7 @@
         return false;
     }
 
-
-    void Printf(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
+    void Printf(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
         std::string s;
 
         va_list ap;
@@ -522,7 +521,7 @@
         line_printer_.Print(s, LinePrinter::INFO);
     }
 
-    void Println(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
+    void Println(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
         std::string s;
 
         va_list ap;
@@ -534,7 +533,7 @@
         line_printer_.KeepInfoLine();
     }
 
-    void Error(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
+    void Error(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
         std::string s = "adb: error: ";
 
         va_list ap;
@@ -545,7 +544,7 @@
         line_printer_.Print(s, LinePrinter::ERROR);
     }
 
-    void Warning(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
+    void Warning(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
         std::string s = "adb: warning: ";
 
         va_list ap;
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/adb/sysdeps.h b/adb/sysdeps.h
index 3be99f6..f1197d7 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -39,19 +39,6 @@
 #include "sysdeps/network.h"
 #include "sysdeps/stat.h"
 
-// Some printf-like functions are implemented in terms of
-// android::base::StringAppendV, so they should use the same attribute for
-// compile-time format string checking. On Windows, if the mingw version of
-// vsnprintf is used in StringAppendV, use `gnu_printf' which allows z in %zd
-// and PRIu64 (and related) to be recognized by the compile-time checking.
-#define ADB_FORMAT_ARCHETYPE __printf__
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-#undef ADB_FORMAT_ARCHETYPE
-#define ADB_FORMAT_ARCHETYPE gnu_printf
-#endif
-#endif
-
 #ifdef _WIN32
 
 // Clang-only nullability specifiers
@@ -212,14 +199,12 @@
 extern int adb_utime(const char *, struct utimbuf *);
 extern int adb_chmod(const char *, int);
 
-extern int adb_vfprintf(FILE *stream, const char *format, va_list ap)
-    __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 0)));
-extern int adb_vprintf(const char *format, va_list ap)
-    __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 1, 0)));
-extern int adb_fprintf(FILE *stream, const char *format, ...)
-    __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3)));
-extern int adb_printf(const char *format, ...)
-    __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 1, 2)));
+extern int adb_vfprintf(FILE* stream, const char* format, va_list ap)
+        __attribute__((__format__(__printf__, 2, 0)));
+extern int adb_vprintf(const char* format, va_list ap) __attribute__((__format__(__printf__, 1, 0)));
+extern int adb_fprintf(FILE* stream, const char* format, ...)
+        __attribute__((__format__(__printf__, 2, 3)));
+extern int adb_printf(const char* format, ...) __attribute__((__format__(__printf__, 1, 2)));
 
 extern int adb_fputs(const char* buf, FILE* stream);
 extern int adb_fputc(int ch, FILE* stream);
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index bfac342..52f586c 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -2455,9 +2455,8 @@
 }
 
 // Function prototype because attributes cannot be placed on func definitions.
-static int _console_vfprintf(const HANDLE console, FILE* stream,
-                             const char *format, va_list ap)
-    __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 3, 0)));
+static int _console_vfprintf(const HANDLE console, FILE* stream, const char* format, va_list ap)
+        __attribute__((__format__(__printf__, 3, 0)));
 
 // Internal function to format a UTF-8 string and write it to a Win32 console.
 // Returns -1 on error.
diff --git a/base/include/android-base/chrono_utils.h b/base/include/android-base/chrono_utils.h
index c3396ee..11fcf71 100644
--- a/base/include/android-base/chrono_utils.h
+++ b/base/include/android-base/chrono_utils.h
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_CHRONO_UTILS_H
-#define ANDROID_BASE_CHRONO_UTILS_H
+#pragma once
 
 #include <chrono>
 #include <sstream>
 
-#if __cplusplus > 201103L  // C++14
+#if __cplusplus > 201103L && !defined(__WIN32)  // C++14
 using namespace std::chrono_literals;
 #endif
 
@@ -52,5 +51,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_CHRONO_UTILS_H
diff --git a/base/include/android-base/endian.h b/base/include/android-base/endian.h
index 6eb677c..cbbd8c9 100644
--- a/base/include/android-base/endian.h
+++ b/base/include/android-base/endian.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_ENDIAN_H
-#define ANDROID_BASE_ENDIAN_H
+#pragma once
 
 /* A cross-platform equivalent of bionic's <sys/endian.h>. */
 
@@ -86,5 +85,3 @@
 #define le64toh(x) (x)
 
 #endif
-
-#endif  // ANDROID_BASE_ENDIAN_H
diff --git a/base/include/android-base/errors.h b/base/include/android-base/errors.h
index 04c299c..06f29fc 100644
--- a/base/include/android-base/errors.h
+++ b/base/include/android-base/errors.h
@@ -27,8 +27,7 @@
 // special handling to get the error string. Refer to Microsoft documentation
 // to determine which error code to check for each function.
 
-#ifndef ANDROID_BASE_ERRORS_H
-#define ANDROID_BASE_ERRORS_H
+#pragma once
 
 #include <string>
 
@@ -42,5 +41,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_ERRORS_H
diff --git a/base/include/android-base/file.h b/base/include/android-base/file.h
index 667d6fb..908690b 100644
--- a/base/include/android-base/file.h
+++ b/base/include/android-base/file.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_FILE_H
-#define ANDROID_BASE_FILE_H
+#pragma once
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -78,5 +77,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif // ANDROID_BASE_FILE_H
diff --git a/base/include/android-base/logging.h b/base/include/android-base/logging.h
index 05a12e7..7f0801f 100644
--- a/base/include/android-base/logging.h
+++ b/base/include/android-base/logging.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_LOGGING_H
-#define ANDROID_BASE_LOGGING_H
+#pragma once
 
 //
 // Google-style C++ logging.
@@ -506,5 +505,3 @@
 #undef OSTREAM_STRING_POINTER_USAGE_WARNING
 
 }  // namespace std
-
-#endif  // ANDROID_BASE_LOGGING_H
diff --git a/base/include/android-base/macros.h b/base/include/android-base/macros.h
index fd6efb2..0c8eac0 100644
--- a/base/include/android-base/macros.h
+++ b/base/include/android-base/macros.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_MACROS_H
-#define ANDROID_BASE_MACROS_H
+#pragma once
 
 #include <stddef.h>  // for size_t
 #include <unistd.h>  // for TEMP_FAILURE_RETRY
@@ -197,5 +196,3 @@
 #elif defined(__mips__) && defined(__LP64__)
 #define ABI_STRING "mips64"
 #endif
-
-#endif  // ANDROID_BASE_MACROS_H
diff --git a/base/include/android-base/memory.h b/base/include/android-base/memory.h
index 9971226..0277a03 100644
--- a/base/include/android-base/memory.h
+++ b/base/include/android-base/memory.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_MEMORY_H
-#define ANDROID_BASE_MEMORY_H
+#pragma once
 
 namespace android {
 namespace base {
@@ -37,5 +36,3 @@
 
 } // namespace base
 } // namespace android
-
-#endif  // ANDROID_BASE_MEMORY_H
diff --git a/base/include/android-base/parsedouble.h b/base/include/android-base/parsedouble.h
index daa6902..c273c61 100644
--- a/base/include/android-base/parsedouble.h
+++ b/base/include/android-base/parsedouble.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_PARSEDOUBLE_H
-#define ANDROID_BASE_PARSEDOUBLE_H
+#pragma once
 
 #include <errno.h>
 #include <stdlib.h>
@@ -46,5 +45,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_PARSEDOUBLE_H
diff --git a/base/include/android-base/parseint.h b/base/include/android-base/parseint.h
index 1b7cc5f..b0fc7c3 100644
--- a/base/include/android-base/parseint.h
+++ b/base/include/android-base/parseint.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_PARSEINT_H
-#define ANDROID_BASE_PARSEINT_H
+#pragma once
 
 #include <errno.h>
 #include <stdlib.h>
@@ -104,5 +103,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_PARSEINT_H
diff --git a/base/include/android-base/parsenetaddress.h b/base/include/android-base/parsenetaddress.h
index b4ac025..47f8b5f 100644
--- a/base/include/android-base/parsenetaddress.h
+++ b/base/include/android-base/parsenetaddress.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_PARSENETADDRESS_H
-#define ANDROID_BASE_PARSENETADDRESS_H
+#pragma once
 
 #include <string>
 
@@ -34,5 +33,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_PARSENETADDRESS_H
diff --git a/base/include/android-base/properties.h b/base/include/android-base/properties.h
index 3a05143..31e5273 100644
--- a/base/include/android-base/properties.h
+++ b/base/include/android-base/properties.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_PROPERTIES_H
-#define ANDROID_BASE_PROPERTIES_H
+#pragma once
 
 #include <sys/cdefs.h>
 
@@ -73,5 +72,3 @@
 
 } // namespace base
 } // namespace android
-
-#endif  // ANDROID_BASE_PROPERTIES_H
diff --git a/base/include/android-base/scopeguard.h b/base/include/android-base/scopeguard.h
index c314e02..e6a9d10 100644
--- a/base/include/android-base/scopeguard.h
+++ b/base/include/android-base/scopeguard.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_SCOPEGUARD_H
-#define ANDROID_BASE_SCOPEGUARD_H
+#pragma once
 
 #include <utility>  // for std::move, std::forward
 
@@ -66,5 +65,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_SCOPEGUARD_H
diff --git a/base/include/android-base/stringprintf.h b/base/include/android-base/stringprintf.h
index 1fd6297..93c56af 100644
--- a/base/include/android-base/stringprintf.h
+++ b/base/include/android-base/stringprintf.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_STRINGPRINTF_H
-#define ANDROID_BASE_STRINGPRINTF_H
+#pragma once
 
 #include <stdarg.h>
 #include <string>
@@ -24,33 +23,18 @@
 namespace base {
 
 // These printf-like functions are implemented in terms of vsnprintf, so they
-// use the same attribute for compile-time format string checking. On Windows,
-// if the mingw version of vsnprintf is used, use `gnu_printf' which allows z
-// in %zd and PRIu64 (and related) to be recognized by the compile-time
-// checking.
-#define ANDROID_BASE_FORMAT_ARCHETYPE __printf__
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-#undef ANDROID_BASE_FORMAT_ARCHETYPE
-#define ANDROID_BASE_FORMAT_ARCHETYPE gnu_printf
-#endif
-#endif
+// use the same attribute for compile-time format string checking.
 
 // Returns a string corresponding to printf-like formatting of the arguments.
-std::string StringPrintf(const char* fmt, ...)
-    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 1, 2)));
+std::string StringPrintf(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2)));
 
 // Appends a printf-like formatting of the arguments to 'dst'.
 void StringAppendF(std::string* dst, const char* fmt, ...)
-    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 2, 3)));
+    __attribute__((__format__(__printf__, 2, 3)));
 
 // Appends a printf-like formatting of the arguments to 'dst'.
 void StringAppendV(std::string* dst, const char* format, va_list ap)
-    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 2, 0)));
-
-#undef ANDROID_BASE_FORMAT_ARCHETYPE
+    __attribute__((__format__(__printf__, 2, 0)));
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_STRINGPRINTF_H
diff --git a/base/include/android-base/strings.h b/base/include/android-base/strings.h
index 4d9fa34..9c35560 100644
--- a/base/include/android-base/strings.h
+++ b/base/include/android-base/strings.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_STRINGS_H
-#define ANDROID_BASE_STRINGS_H
+#pragma once
 
 #include <sstream>
 #include <string>
@@ -75,5 +74,3 @@
 
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_STRINGS_H
diff --git a/base/include/android-base/test_utils.h b/base/include/android-base/test_utils.h
index b29676f..9e2ea97 100644
--- a/base/include/android-base/test_utils.h
+++ b/base/include/android-base/test_utils.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_TEST_UTILS_H
-#define ANDROID_BASE_TEST_UTILS_H
+#pragma once
 
 #include <regex>
 #include <string>
@@ -114,5 +113,3 @@
       ADD_FAILURE() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << (str); \
     }                                                                                             \
   } while (0)
-
-#endif  // ANDROID_BASE_TEST_UTILS_H
diff --git a/base/include/android-base/thread_annotations.h b/base/include/android-base/thread_annotations.h
index 1307f0e..d56e935 100644
--- a/base/include/android-base/thread_annotations.h
+++ b/base/include/android-base/thread_annotations.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_THREAD_ANNOTATIONS_H
-#define ANDROID_BASE_THREAD_ANNOTATIONS_H
+#pragma once
 
 #if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
 #define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))
@@ -109,5 +108,3 @@
 
 #define NO_THREAD_SAFETY_ANALYSIS \
       THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
-
-#endif  // ANDROID_BASE_THREAD_ANNOTATIONS_H
diff --git a/base/include/android-base/threads.h b/base/include/android-base/threads.h
index 85e65ba..f4ba809 100644
--- a/base/include/android-base/threads.h
+++ b/base/include/android-base/threads.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_THREADS_H
-#define ANDROID_BASE_THREADS_H
+#pragma once
 
 #include <stdint.h>
 
@@ -24,5 +23,3 @@
 uint64_t GetThreadId();
 }
 }  // namespace android
-
-#endif
diff --git a/base/include/android-base/unique_fd.h b/base/include/android-base/unique_fd.h
index 5d89271..d334e30 100644
--- a/base/include/android-base/unique_fd.h
+++ b/base/include/android-base/unique_fd.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_UNIQUE_FD_H
-#define ANDROID_BASE_UNIQUE_FD_H
+#pragma once
 
 #include <fcntl.h>
 
@@ -150,5 +149,3 @@
 #endif
     "close called on unique_fd"
   )));
-
-#endif  // ANDROID_BASE_UNIQUE_FD_H
diff --git a/base/include/android-base/utf8.h b/base/include/android-base/utf8.h
index c9cc1ab..4b91623 100755
--- a/base/include/android-base/utf8.h
+++ b/base/include/android-base/utf8.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_BASE_UTF8_H
-#define ANDROID_BASE_UTF8_H
+#pragma once
 
 #ifdef _WIN32
 #include <string>
@@ -102,5 +101,3 @@
 }  // namespace utf8
 }  // namespace base
 }  // namespace android
-
-#endif  // ANDROID_BASE_UTF8_H
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index 2935eb5..50c70f3 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -78,21 +78,10 @@
 void set_verbose();
 
 // These printf-like functions are implemented in terms of vsnprintf, so they
-// use the same attribute for compile-time format string checking. On Windows,
-// if the mingw version of vsnprintf is used, use `gnu_printf' which allows z
-// in %zd and PRIu64 (and related) to be recognized by the compile-time
-// checking.
-#define FASTBOOT_FORMAT_ARCHETYPE __printf__
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-#undef FASTBOOT_FORMAT_ARCHETYPE
-#define FASTBOOT_FORMAT_ARCHETYPE gnu_printf
-#endif
-#endif
+// use the same attribute for compile-time format string checking.
 void die(const char* fmt, ...) __attribute__((__noreturn__))
-__attribute__((__format__(FASTBOOT_FORMAT_ARCHETYPE, 1, 2)));
-void verbose(const char* fmt, ...) __attribute__((__format__(FASTBOOT_FORMAT_ARCHETYPE, 1, 2)));
-#undef FASTBOOT_FORMAT_ARCHETYPE
+__attribute__((__format__(__printf__, 1, 2)));
+void verbose(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2)));
 
 /* Current product */
 extern char cur_product[FB_RESPONSE_SZ + 1];
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index b0b4839..a3ce879 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -38,7 +38,6 @@
     include_dirs: ["system/vold"],
     srcs: [
         "fs_mgr.cpp",
-        "fs_mgr_dm_ioctl.cpp",
         "fs_mgr_format.cpp",
         "fs_mgr_verity.cpp",
         "fs_mgr_avb.cpp",
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 9856126..6417a5c 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -50,6 +50,7 @@
 #include <ext4_utils/ext4_sb.h>
 #include <ext4_utils/ext4_utils.h>
 #include <ext4_utils/wipe.h>
+#include <libdm/dm.h>
 #include <linux/fs.h>
 #include <linux/loop.h>
 #include <linux/magic.h>
@@ -59,7 +60,6 @@
 #include "fs_mgr.h"
 #include "fs_mgr_avb.h"
 #include "fs_mgr_priv.h"
-#include "fs_mgr_priv_dm_ioctl.h"
 
 #define KEY_LOC_PROP   "ro.crypto.keyfile.userdata"
 #define KEY_IN_FOOTER  "footer"
@@ -76,6 +76,8 @@
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
+using DeviceMapper = android::dm::DeviceMapper;
+
 // record fs stat
 enum FsStatFlags {
     FS_STAT_IS_EXT4 = 0x0001,
@@ -802,14 +804,9 @@
         return true;
     }
 
-    android::base::unique_fd dm_fd(open("/dev/device-mapper", O_RDONLY));
-    if (dm_fd < 0) {
-        PLOG(ERROR) << "open /dev/device-mapper failed";
-        return false;
-    }
-    struct dm_ioctl io;
+    DeviceMapper& dm = DeviceMapper::Instance();
     std::string device_name;
-    if (!fs_mgr_dm_get_device_name(&io, rec->blk_device, dm_fd, &device_name)) {
+    if (!dm.GetDmDevicePathByName(rec->blk_device, &device_name)) {
         return false;
     }
     free(rec->blk_device);
@@ -1369,12 +1366,6 @@
         return false;
     }
 
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC)));
-    if (fd == -1) {
-        PERROR << "Error opening device mapper";
-        return false;
-    }
-
     std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
                                                                fs_mgr_free_fstab);
     if (!fstab) {
@@ -1382,8 +1373,8 @@
         return false;
     }
 
-    alignas(dm_ioctl) char buffer[DM_BUF_SIZE];
-    struct dm_ioctl* io = (struct dm_ioctl*)buffer;
+    DeviceMapper& dm = DeviceMapper::Instance();
+
     bool system_root = android::base::GetProperty("ro.build.system_root_image", "") == "true";
 
     for (int i = 0; i < fstab->num_entries; i++) {
@@ -1399,20 +1390,20 @@
             mount_point = basename(fstab->recs[i].mount_point);
         }
 
-        fs_mgr_dm_ioctl_init(io, DM_BUF_SIZE, mount_point);
+        const char* status = nullptr;
 
-        const char* status;
-        if (ioctl(fd, DM_TABLE_STATUS, io)) {
+        std::vector<DeviceMapper::TargetInfo> table;
+        if (!dm.GetTableStatus(mount_point, &table) || table.empty() || table[0].data.empty()) {
             if (fstab->recs[i].fs_mgr_flags & MF_VERIFYATBOOT) {
                 status = "V";
             } else {
                 PERROR << "Failed to query DM_TABLE_STATUS for " << mount_point.c_str();
                 continue;
             }
+        } else {
+            status = table[0].data.c_str();
         }
 
-        status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
-
         // To be consistent in vboot 1.0 and vboot 2.0 (AVB), change the mount_point
         // back to 'system' for the callback. So it has property [partition.system.verified]
         // instead of [partition.vroot.verified].
diff --git a/fs_mgr/fs_mgr_avb.cpp b/fs_mgr/fs_mgr_avb.cpp
index 2020fa6..7c6093e 100644
--- a/fs_mgr/fs_mgr_avb.cpp
+++ b/fs_mgr/fs_mgr_avb.cpp
@@ -33,11 +33,11 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <libavb/libavb.h>
+#include <libdm/dm.h>
 
 #include "fs_mgr.h"
 #include "fs_mgr_priv.h"
 #include "fs_mgr_priv_avb_ops.h"
-#include "fs_mgr_priv_dm_ioctl.h"
 #include "fs_mgr_priv_sha.h"
 
 static inline bool nibble_value(const char& c, uint8_t* value) {
@@ -218,9 +218,9 @@
 // Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel.
 // See the following link for more details:
 // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
-static std::string construct_verity_table(const AvbHashtreeDescriptor& hashtree_desc,
-                                          const std::string& salt, const std::string& root_digest,
-                                          const std::string& blk_device) {
+static bool construct_verity_table(const AvbHashtreeDescriptor& hashtree_desc,
+                                   const std::string& salt, const std::string& root_digest,
+                                   const std::string& blk_device, android::dm::DmTable* table) {
     // Loads androidboot.veritymode from kernel cmdline.
     std::string verity_mode;
     if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) {
@@ -235,145 +235,56 @@
         dm_verity_mode = "ignore_corruption";
     } else if (verity_mode != "eio") {  // Default dm_verity_mode is eio.
         LERROR << "Unknown androidboot.veritymode: " << verity_mode;
-        return "";
+        return false;
     }
 
-    // dm-verity construction parameters:
-    //   <version> <dev> <hash_dev>
-    //   <data_block_size> <hash_block_size>
-    //   <num_data_blocks> <hash_start_block>
-    //   <algorithm> <digest> <salt>
-    //   [<#opt_params> <opt_params>]
-    std::ostringstream verity_table;
-    verity_table << hashtree_desc.dm_verity_version << " " << blk_device << " " << blk_device << " "
-                 << hashtree_desc.data_block_size << " " << hashtree_desc.hash_block_size << " "
-                 << hashtree_desc.image_size / hashtree_desc.data_block_size << " "
-                 << hashtree_desc.tree_offset / hashtree_desc.hash_block_size << " "
-                 << hashtree_desc.hash_algorithm << " " << root_digest << " " << salt;
+    std::ostringstream hash_algorithm;
+    hash_algorithm << hashtree_desc.hash_algorithm;
 
-    // Continued from the above optional parameters:
-    //   [<#opt_params> <opt_params>]
-    int optional_argc = 0;
-    std::ostringstream optional_args;
-
-    // dm-verity optional parameters for FEC (forward error correction):
-    //   use_fec_from_device <fec_dev>
-    //   fec_roots <num>
-    //   fec_blocks <num>
-    //   fec_start <offset>
+    android::dm::DmTargetVerity target(0, hashtree_desc.image_size / 512,
+                                       hashtree_desc.dm_verity_version, blk_device, blk_device,
+                                       hashtree_desc.data_block_size, hashtree_desc.hash_block_size,
+                                       hashtree_desc.image_size / hashtree_desc.data_block_size,
+                                       hashtree_desc.tree_offset / hashtree_desc.hash_block_size,
+                                       hash_algorithm.str(), root_digest, salt);
     if (hashtree_desc.fec_size > 0) {
-        // Note that fec_blocks is the size that FEC covers, *NOT* the
-        // size of the FEC data. Since we use FEC for everything up until
-        // the FEC data, it's the same as the offset (fec_start).
-        optional_argc += 8;
-        // clang-format off
-        optional_args << "use_fec_from_device " << blk_device
-                      << " fec_roots " << hashtree_desc.fec_num_roots
-                      << " fec_blocks " << hashtree_desc.fec_offset / hashtree_desc.data_block_size
-                      << " fec_start " << hashtree_desc.fec_offset / hashtree_desc.data_block_size
-                      << " ";
-        // clang-format on
+        target.UseFec(blk_device, hashtree_desc.fec_num_roots,
+                      hashtree_desc.fec_offset / hashtree_desc.data_block_size,
+                      hashtree_desc.fec_offset / hashtree_desc.data_block_size);
     }
-
     if (!dm_verity_mode.empty()) {
-        optional_argc += 1;
-        optional_args << dm_verity_mode << " ";
+        target.SetVerityMode(dm_verity_mode);
     }
-
     // Always use ignore_zero_blocks.
-    optional_argc += 1;
-    optional_args << "ignore_zero_blocks";
+    target.IgnoreZeroBlocks();
 
-    verity_table << " " << optional_argc << " " << optional_args.str();
-    return verity_table.str();
-}
+    LINFO << "Built verity table: '" << target.GetParameterString() << "'";
 
-static bool load_verity_table(struct dm_ioctl* io, const std::string& dm_device_name, int fd,
-                              uint64_t image_size, const std::string& verity_table) {
-    fs_mgr_dm_ioctl_init(io, DM_BUF_SIZE, dm_device_name);
-
-    // The buffer consists of [dm_ioctl][dm_target_spec][verity_params].
-    char* buffer = (char*)io;
-
-    // Builds the dm_target_spec arguments.
-    struct dm_target_spec* dm_target = (struct dm_target_spec*)&buffer[sizeof(struct dm_ioctl)];
-    io->flags = DM_READONLY_FLAG;
-    io->target_count = 1;
-    dm_target->status = 0;
-    dm_target->sector_start = 0;
-    dm_target->length = image_size / 512;
-    strcpy(dm_target->target_type, "verity");
-
-    // Builds the verity params.
-    char* verity_params = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);
-    size_t bufsize = DM_BUF_SIZE - (verity_params - buffer);
-
-    LINFO << "Loading verity table: '" << verity_table << "'";
-
-    // Copies verity_table to verity_params (including the terminating null byte).
-    if (verity_table.size() > bufsize - 1) {
-        LERROR << "Verity table size too large: " << verity_table.size()
-               << " (max allowable size: " << bufsize - 1 << ")";
-        return false;
-    }
-    memcpy(verity_params, verity_table.c_str(), verity_table.size() + 1);
-
-    // Sets ext target boundary.
-    verity_params += verity_table.size() + 1;
-    verity_params = (char*)(((unsigned long)verity_params + 7) & ~7);
-    dm_target->next = verity_params - buffer;
-
-    // Sends the ioctl to load the verity table.
-    if (ioctl(fd, DM_TABLE_LOAD, io)) {
-        PERROR << "Error loading verity table";
-        return false;
-    }
-
-    return true;
+    return table->AddTarget(std::make_unique<android::dm::DmTargetVerity>(target));
 }
 
 static bool hashtree_dm_verity_setup(struct fstab_rec* fstab_entry,
                                      const AvbHashtreeDescriptor& hashtree_desc,
                                      const std::string& salt, const std::string& root_digest,
                                      bool wait_for_verity_dev) {
-    // Gets the device mapper fd.
-    android::base::unique_fd fd(open("/dev/device-mapper", O_RDWR));
-    if (fd < 0) {
-        PERROR << "Error opening device mapper";
+    android::dm::DmTable table;
+    if (!construct_verity_table(hashtree_desc, salt, root_digest, fstab_entry->blk_device, &table) ||
+        !table.valid()) {
+        LERROR << "Failed to construct verity table.";
         return false;
     }
+    table.set_readonly(true);
 
-    // Creates the device.
-    alignas(dm_ioctl) char buffer[DM_BUF_SIZE];
-    struct dm_ioctl* io = (struct dm_ioctl*)buffer;
     const std::string mount_point(basename(fstab_entry->mount_point));
-    if (!fs_mgr_dm_create_device(io, mount_point, fd)) {
+    android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
+    if (!dm.CreateDevice(mount_point, table)) {
         LERROR << "Couldn't create verity device!";
         return false;
     }
 
-    // Gets the name of the device file.
-    std::string verity_blk_name;
-    if (!fs_mgr_dm_get_device_name(io, mount_point, fd, &verity_blk_name)) {
-        LERROR << "Couldn't get verity device number!";
-        return false;
-    }
-
-    std::string verity_table =
-        construct_verity_table(hashtree_desc, salt, root_digest, fstab_entry->blk_device);
-    if (verity_table.empty()) {
-        LERROR << "Failed to construct verity table.";
-        return false;
-    }
-
-    // Loads the verity mapping table.
-    if (!load_verity_table(io, mount_point, fd, hashtree_desc.image_size, verity_table)) {
-        LERROR << "Couldn't load verity table!";
-        return false;
-    }
-
-    // Activates the device.
-    if (!fs_mgr_dm_resume_table(io, mount_point, fd)) {
+    std::string dev_path;
+    if (!dm.GetDmDevicePathByName(mount_point, &dev_path)) {
+        LERROR << "Couldn't get verity device path!";
         return false;
     }
 
@@ -382,10 +293,10 @@
 
     // Updates fstab_rec->blk_device to verity device name.
     free(fstab_entry->blk_device);
-    fstab_entry->blk_device = strdup(verity_blk_name.c_str());
+    fstab_entry->blk_device = strdup(dev_path.c_str());
 
     // Makes sure we've set everything up properly.
-    if (wait_for_verity_dev && !fs_mgr_wait_for_file(verity_blk_name, 1s)) {
+    if (wait_for_verity_dev && !fs_mgr_wait_for_file(dev_path, 1s)) {
         return false;
     }
 
diff --git a/fs_mgr/fs_mgr_dm_ioctl.cpp b/fs_mgr/fs_mgr_dm_ioctl.cpp
deleted file mode 100644
index 3a7fae4..0000000
--- a/fs_mgr/fs_mgr_dm_ioctl.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2016 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 <errno.h>
-#include <string.h>
-
-#include <android-base/logging.h>
-#include <sys/ioctl.h>
-
-#include "fs_mgr_priv.h"
-#include "fs_mgr_priv_dm_ioctl.h"
-
-void fs_mgr_dm_ioctl_init(struct dm_ioctl* io, size_t size, const std::string& name) {
-    memset(io, 0, size);
-    io->data_size = size;
-    io->data_start = sizeof(struct dm_ioctl);
-    io->version[0] = 4;
-    io->version[1] = 0;
-    io->version[2] = 0;
-    if (!name.empty()) {
-        strlcpy(io->name, name.c_str(), sizeof(io->name));
-    }
-}
-
-bool fs_mgr_dm_create_device(struct dm_ioctl* io, const std::string& name, int fd) {
-    fs_mgr_dm_ioctl_init(io, sizeof(*io), name);
-    if (ioctl(fd, DM_DEV_CREATE, io)) {
-        PERROR << "Error creating device mapping";
-        return false;
-    }
-    return true;
-}
-
-bool fs_mgr_dm_destroy_device(struct dm_ioctl* io, const std::string& name, int fd) {
-    fs_mgr_dm_ioctl_init(io, sizeof(*io), name);
-    if (ioctl(fd, DM_DEV_REMOVE, io)) {
-        PERROR << "Error removing device mapping";
-        return false;
-    }
-    return true;
-}
-
-bool fs_mgr_dm_get_device_name(struct dm_ioctl* io, const std::string& name, int fd,
-                               std::string* out_dev_name) {
-    FS_MGR_CHECK(out_dev_name != nullptr);
-
-    fs_mgr_dm_ioctl_init(io, sizeof(*io), name);
-    if (ioctl(fd, DM_DEV_STATUS, io)) {
-        PERROR << "Error fetching device-mapper device number";
-        return false;
-    }
-
-    int dev_num = (io->dev & 0xff) | ((io->dev >> 12) & 0xfff00);
-    *out_dev_name = "/dev/block/dm-" + std::to_string(dev_num);
-
-    return true;
-}
-
-bool fs_mgr_dm_resume_table(struct dm_ioctl* io, const std::string& name, int fd) {
-    fs_mgr_dm_ioctl_init(io, sizeof(*io), name);
-    if (ioctl(fd, DM_DEV_SUSPEND, io)) {
-        PERROR << "Error activating device table";
-        return false;
-    }
-    return true;
-}
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index ed42d40..5159b4c 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -40,93 +40,32 @@
 #include <liblp/reader.h>
 
 #include "fs_mgr_priv.h"
-#include "fs_mgr_priv_dm_ioctl.h"
 
 namespace android {
 namespace fs_mgr {
 
-std::string LogicalPartitionExtent::Serialize() const {
-    // Note: we need to include an explicit null-terminator.
-    std::string argv =
-        android::base::StringPrintf("%s %" PRIu64, block_device_.c_str(), first_sector_);
-    argv.push_back(0);
+using DeviceMapper = android::dm::DeviceMapper;
+using DmTable = android::dm::DmTable;
+using DmTarget = android::dm::DmTarget;
+using DmTargetZero = android::dm::DmTargetZero;
+using DmTargetLinear = android::dm::DmTargetLinear;
 
-    // The kernel expects each target to be aligned.
-    size_t spec_bytes = sizeof(struct dm_target_spec) + argv.size();
-    size_t padding = ((spec_bytes + 7) & ~7) - spec_bytes;
-    for (size_t i = 0; i < padding; i++) {
-        argv.push_back(0);
-    }
-
-    struct dm_target_spec spec;
-    spec.sector_start = logical_sector_;
-    spec.length = num_sectors_;
-    spec.status = 0;
-    strcpy(spec.target_type, "linear");
-    spec.next = sizeof(struct dm_target_spec) + argv.size();
-
-    return std::string((char*)&spec, sizeof(spec)) + argv;
-}
-
-static bool LoadDmTable(int dm_fd, const LogicalPartition& partition) {
-    // Combine all dm_target_spec buffers together.
-    std::string target_string;
+static bool CreateDmDeviceForPartition(DeviceMapper& dm, const LogicalPartition& partition) {
+    DmTable table;
     for (const auto& extent : partition.extents) {
-        target_string += extent.Serialize();
+        table.AddTarget(std::make_unique<DmTargetLinear>(extent));
     }
-
-    // Allocate the ioctl buffer.
-    size_t buffer_size = sizeof(struct dm_ioctl) + target_string.size();
-    std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(buffer_size);
-
-    // Initialize the ioctl buffer header, then copy our target specs in.
-    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get());
-    fs_mgr_dm_ioctl_init(io, buffer_size, partition.name);
-    io->target_count = partition.extents.size();
-    if (partition.attributes & kPartitionReadonly) {
-        io->flags |= DM_READONLY_FLAG;
-    }
-    memcpy(io + 1, target_string.c_str(), target_string.size());
-
-    if (ioctl(dm_fd, DM_TABLE_LOAD, io)) {
-        PERROR << "Failed ioctl() on DM_TABLE_LOAD, partition " << partition.name;
+    if (!dm.CreateDevice(partition.name, table)) {
         return false;
     }
-    return true;
-}
-
-static bool LoadTablesAndActivate(int dm_fd, const LogicalPartition& partition) {
-    if (!LoadDmTable(dm_fd, partition)) {
-        return false;
-    }
-
-    struct dm_ioctl io;
-    return fs_mgr_dm_resume_table(&io, partition.name, dm_fd);
-}
-
-static bool CreateDmDeviceForPartition(int dm_fd, const LogicalPartition& partition) {
-    struct dm_ioctl io;
-    if (!fs_mgr_dm_create_device(&io, partition.name, dm_fd)) {
-        return false;
-    }
-    if (!LoadTablesAndActivate(dm_fd, partition)) {
-        // Remove the device rather than leave it in an inactive state.
-        fs_mgr_dm_destroy_device(&io, partition.name, dm_fd);
-        return false;
-    }
-
     LINFO << "Created device-mapper device: " << partition.name;
     return true;
 }
 
 bool CreateLogicalPartitions(const LogicalPartitionTable& table) {
-    android::base::unique_fd dm_fd(open("/dev/device-mapper", O_RDWR));
-    if (dm_fd < 0) {
-        PLOG(ERROR) << "failed to open /dev/device-mapper";
-        return false;
-    }
+    DeviceMapper& dm = DeviceMapper::Instance();
     for (const auto& partition : table.partitions) {
-        if (!CreateDmDeviceForPartition(dm_fd, partition)) {
+        if (!CreateDmDeviceForPartition(dm, partition)) {
             LOG(ERROR) << "could not create dm-linear device for partition: " << partition.name;
             return false;
         }
@@ -138,6 +77,35 @@
     return nullptr;
 }
 
+static bool CreateDmTable(const std::string& block_device, const LpMetadata& metadata,
+                          const LpMetadataPartition& partition, DmTable* table) {
+    uint64_t sector = 0;
+    for (size_t i = 0; i < partition.num_extents; i++) {
+        const auto& extent = metadata.extents[partition.first_extent_index + i];
+        std::unique_ptr<DmTarget> target;
+        switch (extent.target_type) {
+            case LP_TARGET_TYPE_ZERO:
+                target = std::make_unique<DmTargetZero>(sector, extent.num_sectors);
+                break;
+            case LP_TARGET_TYPE_LINEAR:
+                target = std::make_unique<DmTargetLinear>(sector, extent.num_sectors, block_device,
+                                                          extent.target_data);
+                break;
+            default:
+                LOG(ERROR) << "Unknown target type in metadata: " << extent.target_type;
+                return false;
+        }
+        if (!table->AddTarget(std::move(target))) {
+            return false;
+        }
+        sector += extent.num_sectors;
+    }
+    if (partition.attributes & LP_PARTITION_ATTR_READONLY) {
+        table->set_readonly(true);
+    }
+    return true;
+}
+
 bool CreateLogicalPartitions(const std::string& block_device) {
     uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
     auto metadata = ReadMetadata(block_device.c_str(), slot);
@@ -146,21 +114,21 @@
         return true;
     }
 
-    LogicalPartitionTable table;
+    DeviceMapper& dm = DeviceMapper::Instance();
     for (const auto& partition : metadata->partitions) {
-        LogicalPartition new_partition;
-        new_partition.name = GetPartitionName(partition);
-        new_partition.attributes = partition.attributes;
-        for (size_t i = 0; i < partition.num_extents; i++) {
-            const auto& extent = metadata->extents[partition.first_extent_index + i];
-            new_partition.extents.emplace_back(new_partition.num_sectors, extent.target_data,
-                                               extent.num_sectors, block_device.c_str());
-            new_partition.num_sectors += extent.num_sectors;
+        DmTable table;
+        if (!CreateDmTable(block_device, *metadata.get(), partition, &table)) {
+            return false;
         }
-        table.partitions.push_back(new_partition);
+        std::string name = GetPartitionName(partition);
+        if (!dm.CreateDevice(name, table)) {
+            return false;
+        }
+        std::string path;
+        dm.GetDmDevicePathByName(partition.name, &path);
+        LINFO << "Created logical partition " << name << " on device " << path;
     }
-
-    return CreateLogicalPartitions(table);
+    return true;
 }
 
 }  // namespace fs_mgr
diff --git a/fs_mgr/fs_mgr_priv_dm_ioctl.h b/fs_mgr/fs_mgr_priv_dm_ioctl.h
deleted file mode 100644
index 792475d..0000000
--- a/fs_mgr/fs_mgr_priv_dm_ioctl.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#ifndef __CORE_FS_MGR_PRIV_DM_IOCTL_H
-#define __CORE_FS_MGR_PRIV_DM_IOCTL_H
-
-#include <linux/dm-ioctl.h>
-#include <string>
-
-void fs_mgr_dm_ioctl_init(struct dm_ioctl* io, size_t size, const std::string& name);
-
-bool fs_mgr_dm_create_device(struct dm_ioctl* io, const std::string& name, int fd);
-
-bool fs_mgr_dm_destroy_device(struct dm_ioctl* io, const std::string& name, int fd);
-
-bool fs_mgr_dm_get_device_name(struct dm_ioctl* io, const std::string& name, int fd,
-                               std::string* out_dev_name);
-
-bool fs_mgr_dm_resume_table(struct dm_ioctl* io, const std::string& name, int fd);
-
-#endif /* __CORE_FS_MGR_PRIV_DM_IOCTL_H */
diff --git a/fs_mgr/fs_mgr_verity.cpp b/fs_mgr/fs_mgr_verity.cpp
index fe41f8a..5fb4ebb 100644
--- a/fs_mgr/fs_mgr_verity.cpp
+++ b/fs_mgr/fs_mgr_verity.cpp
@@ -35,6 +35,7 @@
 #include <android-base/unique_fd.h>
 #include <crypto_utils/android_pubkey.h>
 #include <cutils/properties.h>
+#include <libdm/dm.h>
 #include <logwrap/logwrap.h>
 #include <openssl/obj_mac.h>
 #include <openssl/rsa.h>
@@ -44,7 +45,6 @@
 
 #include "fs_mgr.h"
 #include "fs_mgr_priv.h"
-#include "fs_mgr_priv_dm_ioctl.h"
 
 #define VERITY_TABLE_RSA_KEY "/verity_key"
 #define VERITY_TABLE_HASH_IDX 8
@@ -250,48 +250,27 @@
     return true;
 }
 
-static int load_verity_table(struct dm_ioctl *io, const std::string &name,
-                             uint64_t device_size, int fd,
-        const struct verity_table_params *params, format_verity_table_func format)
-{
-    char *verity_params;
-    char *buffer = (char*) io;
-    size_t bufsize;
+static int load_verity_table(android::dm::DeviceMapper& dm, const std::string& name,
+                             uint64_t device_size, const struct verity_table_params* params,
+                             format_verity_table_func format) {
+    android::dm::DmTable table;
+    table.set_readonly(true);
 
-    fs_mgr_dm_ioctl_init(io, DM_BUF_SIZE, name);
-
-    struct dm_target_spec *tgt = (struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)];
-
-    // set tgt arguments
-    io->target_count = 1;
-    io->flags = DM_READONLY_FLAG;
-    tgt->status = 0;
-    tgt->sector_start = 0;
-    tgt->length = device_size / 512;
-    strcpy(tgt->target_type, "verity");
-
-    // build the verity params
-    verity_params = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);
-    bufsize = DM_BUF_SIZE - (verity_params - buffer);
-
-    if (!format(verity_params, bufsize, params)) {
+    char buffer[DM_BUF_SIZE];
+    if (!format(buffer, sizeof(buffer), params)) {
         LERROR << "Failed to format verity parameters";
         return -1;
     }
 
-    LINFO << "loading verity table: '" << verity_params << "'";
-
-    // set next target boundary
-    verity_params += strlen(verity_params) + 1;
-    verity_params = (char*)(((uintptr_t)verity_params + 7) & ~7);
-    tgt->next = verity_params - buffer;
-
-    // send the ioctl to load the verity table
-    if (ioctl(fd, DM_TABLE_LOAD, io)) {
-        PERROR << "Error loading verity table";
+    android::dm::DmTargetVerityString target(0, device_size / 512, buffer);
+    if (!table.AddTarget(std::make_unique<decltype(target)>(target))) {
+        LERROR << "Failed to add verity target";
         return -1;
     }
-
+    if (!dm.CreateDevice(name, table)) {
+        LERROR << "Failed to create verity device \"" << name << "\"";
+        return -1;
+    }
     return 0;
 }
 
@@ -761,11 +740,11 @@
     struct fec_verity_metadata verity;
     struct verity_table_params params = { .table = NULL };
 
-    alignas(dm_ioctl) char buffer[DM_BUF_SIZE];
-    struct dm_ioctl *io = (struct dm_ioctl *) buffer;
     const std::string mount_point(basename(fstab->mount_point));
     bool verified_at_boot = false;
 
+    android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
+
     if (fec_open(&f, fstab->blk_device, O_RDONLY, FEC_VERITY_DISABLE,
             FEC_DEFAULT_ROOTS) < 0) {
         PERROR << "Failed to open '" << fstab->blk_device << "'";
@@ -798,24 +777,6 @@
 
     params.ecc_dev = fstab->blk_device;
 
-    // get the device mapper fd
-    if ((fd = open("/dev/device-mapper", O_RDWR)) < 0) {
-        PERROR << "Error opening device mapper";
-        goto out;
-    }
-
-    // create the device
-    if (!fs_mgr_dm_create_device(io, mount_point, fd)) {
-        LERROR << "Couldn't create verity device!";
-        goto out;
-    }
-
-    // get the name of the device file
-    if (!fs_mgr_dm_get_device_name(io, mount_point, fd, &verity_blk_name)) {
-        LERROR << "Couldn't get verity device number!";
-        goto out;
-    }
-
     if (load_verity_state(fstab, &params.mode) < 0) {
         /* if accessing or updating the state failed, switch to the default
          * safe mode. This makes sure the device won't end up in an endless
@@ -861,8 +822,7 @@
                                    fstab->fs_mgr_flags & MF_SLOTSELECT);
 
     // load the verity mapping table
-    if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
-            format_verity_table) == 0) {
+    if (load_verity_table(dm, mount_point, verity.data_size, &params, format_verity_table) == 0) {
         goto loaded;
     }
 
@@ -871,15 +831,14 @@
         LINFO << "Disabling error correction for " << mount_point.c_str();
         params.ecc.valid = false;
 
-        if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
-                format_verity_table) == 0) {
+        if (load_verity_table(dm, mount_point, verity.data_size, &params, format_verity_table) == 0) {
             goto loaded;
         }
     }
 
     // try the legacy format for backwards compatibility
-    if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
-            format_legacy_verity_table) == 0) {
+    if (load_verity_table(dm, mount_point, verity.data_size, &params, format_legacy_verity_table) ==
+        0) {
         goto loaded;
     }
 
@@ -888,8 +847,8 @@
         LINFO << "Falling back to EIO mode for " << mount_point.c_str();
         params.mode = VERITY_MODE_EIO;
 
-        if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
-                format_legacy_verity_table) == 0) {
+        if (load_verity_table(dm, mount_point, verity.data_size, &params,
+                              format_legacy_verity_table) == 0) {
             goto loaded;
         }
     }
@@ -898,9 +857,8 @@
     goto out;
 
 loaded:
-
-    // activate the device
-    if (!fs_mgr_dm_resume_table(io, mount_point, fd)) {
+    if (!dm.GetDmDevicePathByName(mount_point, &verity_blk_name)) {
+        LERROR << "Couldn't get verity device number!";
         goto out;
     }
 
@@ -923,7 +881,7 @@
     if (!verified_at_boot) {
         free(fstab->blk_device);
         fstab->blk_device = strdup(verity_blk_name.c_str());
-    } else if (!fs_mgr_dm_destroy_device(io, mount_point, fd)) {
+    } else if (!dm.DeleteDevice(mount_point)) {
         LERROR << "Failed to remove verity device " << mount_point.c_str();
         goto out;
     }
diff --git a/fs_mgr/include/fs_mgr_dm_linear.h b/fs_mgr/include/fs_mgr_dm_linear.h
index 9772c4b..3b0c791 100644
--- a/fs_mgr/include/fs_mgr_dm_linear.h
+++ b/fs_mgr/include/fs_mgr_dm_linear.h
@@ -26,53 +26,20 @@
 #define __CORE_FS_MGR_DM_LINEAR_H
 
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 #include <vector>
 
+#include <libdm/dm.h>
+#include <liblp/metadata_format.h>
+
 namespace android {
 namespace fs_mgr {
 
-static const uint32_t kPartitionReadonly = 0x1;
-
-class LogicalPartitionExtent {
-  public:
-    LogicalPartitionExtent() : logical_sector_(0), first_sector_(0), num_sectors_(0) {}
-    LogicalPartitionExtent(uint64_t logical_sector, uint64_t first_sector, uint64_t num_sectors,
-                           const std::string& block_device)
-        : logical_sector_(logical_sector),
-          first_sector_(first_sector),
-          num_sectors_(num_sectors),
-          block_device_(block_device) {}
-
-    // Return a string containing the dm_target_spec buffer needed to use this
-    // extent in a device-mapper table.
-    std::string Serialize() const;
-
-    const std::string& block_device() const { return block_device_; }
-
-  private:
-    // Logical sector this extent represents in the presented block device.
-    // This is equal to the previous extent's logical sector plus the number
-    // of sectors in that extent. The first extent always starts at 0.
-    uint64_t logical_sector_;
-    // First 512-byte sector of this extent, on the source block device.
-    uint64_t first_sector_;
-    // Number of 512-byte sectors.
-    uint64_t num_sectors_;
-    // Target block device.
-    std::string block_device_;
-};
-
 struct LogicalPartition {
-    LogicalPartition() : attributes(0), num_sectors(0) {}
-
     std::string name;
-    uint32_t attributes;
-    // Number of 512-byte sectors total.
-    uint64_t num_sectors;
-    // List of extents.
-    std::vector<LogicalPartitionExtent> extents;
+    std::vector<android::dm::DmTargetLinear> extents;
 };
 
 struct LogicalPartitionTable {
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 672d401..22af123 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"],
@@ -27,7 +28,8 @@
     srcs: [
         "dm_table.cpp",
         "dm_target.cpp",
-        "dm.cpp"
+        "dm.cpp",
+        "loop_control.cpp",
     ],
 
     header_libs: [
@@ -35,3 +37,18 @@
         "liblog_headers",
     ],
 }
+
+cc_test {
+    name: "libdm_test",
+    defaults: ["fs_mgr_defaults"],
+    static_libs: [
+        "libdm",
+        "libbase",
+        "liblog",
+    ],
+    srcs: [
+        "dm_test.cpp",
+        "loop_control_test.cpp",
+        "test_util.cpp",
+    ]
+}
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index e8bae60..ad3b6f4 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 {
@@ -105,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
@@ -160,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;
         }
@@ -237,8 +259,57 @@
 
 // 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;
+}
+
+bool DeviceMapper::GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
+    char buffer[4096];
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer);
+
+    InitIo(io, name);
+    io->data_size = sizeof(buffer);
+    io->data_start = sizeof(*io);
+    if (ioctl(fd_, DM_TABLE_STATUS, io) < 0) {
+        PLOG(ERROR) << "DM_TABLE_STATUS failed for " << name;
+        return false;
+    }
+    if (io->flags & DM_BUFFER_FULL_FLAG) {
+        PLOG(ERROR) << "DM_TABLE_STATUS result for " << name << " was too large";
+        return false;
+    }
+
+    uint32_t cursor = io->data_start;
+    uint32_t data_end = std::min(io->data_size, uint32_t(sizeof(buffer)));
+    for (uint32_t i = 0; i < io->target_count; i++) {
+        if (cursor + sizeof(struct dm_target_spec) > data_end) {
+            break;
+        }
+        // After each dm_target_spec is a status string. spec->next is an
+        // offset from |io->data_start|, and we clamp it to the size of our
+        // buffer.
+        struct dm_target_spec* spec = reinterpret_cast<struct dm_target_spec*>(buffer + cursor);
+        uint32_t data_offset = cursor + sizeof(dm_target_spec);
+        uint32_t next_cursor = std::min(io->data_start + spec->next, data_end);
+
+        std::string data;
+        if (next_cursor > data_offset) {
+            // Note: we use c_str() to eliminate any extra trailing 0s.
+            data = std::string(buffer + data_offset, next_cursor - data_offset).c_str();
+        }
+        table->emplace_back(*spec, data);
+        cursor = next_cursor;
+    }
+    return true;
 }
 
 // private methods of DeviceMapper
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index 14b3932..15c7ce1 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -14,18 +14,19 @@
  * 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) {
+    if (!target->Valid()) {
+        return false;
+    }
+    targets_.push_back(std::move(target));
     return true;
 }
 
@@ -34,21 +35,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..20b26df 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -14,16 +14,102 @@
  * limitations under the License.
  */
 
+#include "libdm/dm_target.h"
+
 #include <android-base/logging.h>
 #include <android-base/macros.h>
+#include <android-base/strings.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_);
+}
+
+DmTargetVerity::DmTargetVerity(uint64_t start, uint64_t length, uint32_t version,
+                               const std::string& block_device, const std::string& hash_device,
+                               uint32_t data_block_size, uint32_t hash_block_size,
+                               uint32_t num_data_blocks, uint32_t hash_start_block,
+                               const std::string& hash_algorithm, const std::string& root_digest,
+                               const std::string& salt)
+    : DmTarget(start, length), valid_(true) {
+    base_args_ = {
+            std::to_string(version),
+            block_device,
+            hash_device,
+            std::to_string(data_block_size),
+            std::to_string(hash_block_size),
+            std::to_string(num_data_blocks),
+            std::to_string(hash_start_block),
+            hash_algorithm,
+            root_digest,
+            salt,
+    };
+}
+
+void DmTargetVerity::UseFec(const std::string& device, uint32_t num_roots, uint32_t num_blocks,
+                            uint32_t start) {
+    optional_args_.emplace_back("use_fec_from_device");
+    optional_args_.emplace_back(device);
+    optional_args_.emplace_back("fec_roots");
+    optional_args_.emplace_back(std::to_string(num_roots));
+    optional_args_.emplace_back("fec_blocks");
+    optional_args_.emplace_back(std::to_string(num_blocks));
+    optional_args_.emplace_back("fec_start");
+    optional_args_.emplace_back(std::to_string(start));
+}
+
+void DmTargetVerity::SetVerityMode(const std::string& mode) {
+    if (mode != "restart_on_corruption" && mode != "ignore_corruption") {
+        LOG(ERROR) << "Unknown verity mode: " << mode;
+        valid_ = false;
+        return;
+    }
+    optional_args_.emplace_back(mode);
+}
+
+void DmTargetVerity::IgnoreZeroBlocks() {
+    optional_args_.emplace_back("ignore_zero_blocks");
+}
+
+std::string DmTargetVerity::GetParameterString() const {
+    std::string base = android::base::Join(base_args_, " ");
+    if (optional_args_.empty()) {
+        return base;
+    }
+    std::string optional = android::base::Join(optional_args_, " ");
+    return base + " " + std::to_string(optional_args_.size()) + " " + optional;
+}
+
+}  // 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..cc61917
--- /dev/null
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -0,0 +1,202 @@
+/*
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <ctime>
+#include <map>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libdm/loop_control.h>
+#include "test_util.h"
+
+using namespace std;
+using namespace android::dm;
+using unique_fd = android::base::unique_fd;
+
+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());
+}
+
+// Helper to ensure that device mapper devices are released.
+class TempDevice {
+  public:
+    TempDevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table);
+    }
+    TempDevice(TempDevice&& other) : dm_(other.dm_), name_(other.name_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~TempDevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    bool WaitForUdev() const {
+        auto start_time = std::chrono::steady_clock::now();
+        while (true) {
+            if (!access(path().c_str(), F_OK)) {
+                return true;
+            }
+            if (errno != ENOENT) {
+                return false;
+            }
+            std::this_thread::sleep_for(50ms);
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start_time;
+            if (elapsed >= 5s) {
+                return false;
+            }
+        }
+    }
+    std::string path() const {
+        std::string device_path;
+        if (!dm_.GetDmDevicePathByName(name_, &device_path)) {
+            return "";
+        }
+        return device_path;
+    }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
+
+    TempDevice(const TempDevice&) = delete;
+    TempDevice& operator=(const TempDevice&) = delete;
+
+    TempDevice& operator=(TempDevice&& other) {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
+    }
+
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    bool valid_;
+};
+
+TEST(libdm, DmLinear) {
+    unique_fd tmp1(CreateTempFile("file_1", 4096));
+    ASSERT_GE(tmp1, 0);
+    unique_fd tmp2(CreateTempFile("file_2", 4096));
+    ASSERT_GE(tmp2, 0);
+
+    // Create two different files. These will back two separate loop devices.
+    const char message1[] = "Hello! This is sector 1.";
+    const char message2[] = "Goodbye. This is sector 2.";
+    ASSERT_TRUE(android::base::WriteFully(tmp1, message1, sizeof(message1)));
+    ASSERT_TRUE(android::base::WriteFully(tmp2, message2, sizeof(message2)));
+
+    LoopDevice loop_a(tmp1);
+    ASSERT_TRUE(loop_a.valid());
+    LoopDevice loop_b(tmp2);
+    ASSERT_TRUE(loop_b.valid());
+
+    // Define a 2-sector device, with each sector mapping to the first sector
+    // of one of our loop devices.
+    DmTable table;
+    ASSERT_TRUE(table.AddTarget(make_unique<DmTargetLinear>(0, 1, loop_a.device(), 0)));
+    ASSERT_TRUE(table.AddTarget(make_unique<DmTargetLinear>(1, 1, loop_b.device(), 0)));
+    ASSERT_TRUE(table.valid());
+
+    TempDevice dev("libdm-test-dm-linear", table);
+    ASSERT_TRUE(dev.valid());
+    ASSERT_FALSE(dev.path().empty());
+    ASSERT_TRUE(dev.WaitForUdev());
+
+    // Note: a scope is needed to ensure that there are no open descriptors
+    // when we go to close the device.
+    {
+        unique_fd dev_fd(open(dev.path().c_str(), O_RDWR));
+        ASSERT_GE(dev_fd, 0);
+
+        // Test that each sector of our device is correctly mapped to each loop
+        // device.
+        char sector[512];
+        ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector)));
+        ASSERT_EQ(strncmp(sector, message1, sizeof(message1)), 0);
+        ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector)));
+        ASSERT_EQ(strncmp(sector, message2, sizeof(message2)), 0);
+    }
+
+    // Test GetTableStatus.
+    DeviceMapper& dm = DeviceMapper::Instance();
+    vector<DeviceMapper::TargetInfo> targets;
+    ASSERT_TRUE(dm.GetTableStatus(dev.name(), &targets));
+    ASSERT_EQ(targets.size(), 2);
+    EXPECT_EQ(strcmp(targets[0].spec.target_type, "linear"), 0);
+    EXPECT_TRUE(targets[0].data.empty());
+    EXPECT_EQ(targets[0].spec.sector_start, 0);
+    EXPECT_EQ(targets[0].spec.length, 1);
+    EXPECT_EQ(strcmp(targets[1].spec.target_type, "linear"), 0);
+    EXPECT_TRUE(targets[1].data.empty());
+    EXPECT_EQ(targets[1].spec.sector_start, 1);
+    EXPECT_EQ(targets[1].spec.length, 1);
+
+    // Normally the TestDevice destructor would delete this, but at least one
+    // test should ensure that device deletion works.
+    ASSERT_TRUE(dev.Destroy());
+}
+
+TEST(libdm, DmVerityArgsAvb2) {
+    std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
+    std::string algorithm = "sha1";
+    std::string digest = "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21";
+    std::string salt = "cc99f81ecb9484220a003b0719ee59dcf9be7e5d";
+
+    DmTargetVerity target(0, 10000, 1, device, device, 4096, 4096, 125961, 125961, algorithm,
+                          digest, salt);
+    target.UseFec(device, 2, 126955, 126955);
+    target.SetVerityMode("restart_on_corruption");
+    target.IgnoreZeroBlocks();
+
+    // Verity table from a walleye build.
+    std::string expected =
+            "1 /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a "
+            "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a 4096 4096 125961 125961 sha1 "
+            "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21 cc99f81ecb9484220a003b0719ee59dcf9be7e5d 10 "
+            "use_fec_from_device /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a fec_roots "
+            "2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks";
+    EXPECT_EQ(target.GetParameterString(), expected);
+}
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 79%
rename from fs_mgr/libdm/include/dm.h
rename to fs_mgr/libdm/include/libdm/dm.h
index 52a9a11..e2bc729 100644
--- a/fs_mgr/libdm/include/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -17,18 +17,21 @@
 #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 <utility>
+#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 +70,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 +83,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 +105,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();
@@ -117,6 +118,18 @@
         }
     }
 
+    // Query the status of a table, given a device name. The output vector will
+    // contain one TargetInfo for each target in the table. If the device does
+    // not exist, or there were too many targets, the call will fail and return
+    // false.
+    struct TargetInfo {
+        struct dm_target_spec spec;
+        std::string data;
+        TargetInfo(const struct dm_target_spec& spec, const std::string& data)
+            : spec(spec), data(data) {}
+    };
+    bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
+
   private:
     // Maximum possible device mapper targets registered in the kernel.
     // This is only used to read the list of targets from kernel so we allocate
@@ -138,6 +151,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..d5974f4
--- /dev/null
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -0,0 +1,150 @@
+/*
+ *  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 <vector>
+
+#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;
+
+    virtual bool Valid() const { return true; }
+
+  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;
+    const std::string& block_device() const { return block_device_; }
+
+  private:
+    std::string block_device_;
+    uint64_t physical_sector_;
+};
+
+class DmTargetVerity final : public DmTarget {
+  public:
+    DmTargetVerity(uint64_t start, uint64_t length, uint32_t version,
+                   const std::string& block_device, const std::string& hash_device,
+                   uint32_t data_block_size, uint32_t hash_block_size, uint32_t num_data_blocks,
+                   uint32_t hash_start_block, const std::string& hash_algorithm,
+                   const std::string& root_digest, const std::string& salt);
+
+    void UseFec(const std::string& device, uint32_t num_roots, uint32_t num_blocks, uint32_t start);
+    void SetVerityMode(const std::string& mode);
+    void IgnoreZeroBlocks();
+
+    std::string name() const override { return "verity"; }
+    std::string GetParameterString() const override;
+    bool Valid() const override { return valid_; }
+
+  private:
+    std::vector<std::string> base_args_;
+    std::vector<std::string> optional_args_;
+    bool valid_;
+};
+
+// This is the same as DmTargetVerity, but the table may be specified as a raw
+// string. This code exists only for fs_mgr_verity and should be avoided. Use
+// DmTargetVerity for new code instead.
+class DmTargetVerityString final : public DmTarget {
+  public:
+    DmTargetVerityString(uint64_t start, uint64_t length, const std::string& target_string)
+        : DmTarget(start, length), target_string_(target_string) {}
+
+    std::string name() const override { return "verity"; }
+    std::string GetParameterString() const override { return target_string_; }
+    bool Valid() const override { return true; }
+
+  private:
+    std::string target_string_;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_DMTARGET_H_ */
diff --git a/fs_mgr/libdm/include/libdm/loop_control.h b/fs_mgr/libdm/include/libdm/loop_control.h
new file mode 100644
index 0000000..e6e83f4
--- /dev/null
+++ b/fs_mgr/libdm/include/libdm/loop_control.h
@@ -0,0 +1,82 @@
+/*
+ *  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_LOOP_CONTROL_H_
+#define _LIBDM_LOOP_CONTROL_H_
+
+#include <string>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace dm {
+
+class LoopControl final {
+  public:
+    LoopControl();
+
+    // Attaches the file specified by 'file_fd' to the loop device specified
+    // by 'loopdev'
+    bool Attach(int file_fd, std::string* loopdev) const;
+
+    // Detach the loop device given by 'loopdev' from the attached backing file.
+    bool Detach(const std::string& loopdev) const;
+
+    LoopControl(const LoopControl&) = delete;
+    LoopControl& operator=(const LoopControl&) = delete;
+    LoopControl& operator=(LoopControl&&) = default;
+    LoopControl(LoopControl&&) = default;
+
+  private:
+    bool FindFreeLoopDevice(std::string* loopdev) const;
+
+    static constexpr const char* kLoopControlDevice = "/dev/loop-control";
+
+    android::base::unique_fd control_fd_;
+};
+
+// Create a temporary loop device around a file descriptor or path.
+class LoopDevice {
+  public:
+    // Create a loop device for the given file descriptor. It is closed when
+    // LoopDevice is destroyed only if auto_close is true.
+    LoopDevice(int fd, bool auto_close = false);
+    // Create a loop device for the given file path. It will be opened for
+    // reading and writing and closed when the loop device is detached.
+    explicit LoopDevice(const std::string& path);
+    ~LoopDevice();
+
+    bool valid() const { return fd_ != -1 && !device_.empty(); }
+    const std::string& device() const { return device_; }
+
+    LoopDevice(const LoopDevice&) = delete;
+    LoopDevice& operator=(const LoopDevice&) = delete;
+    LoopDevice& operator=(LoopDevice&&) = default;
+    LoopDevice(LoopDevice&&) = default;
+
+  private:
+    void Init();
+
+    android::base::unique_fd fd_;
+    bool owns_fd_;
+    std::string device_;
+    LoopControl control_;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_LOOP_CONTROL_H_ */
diff --git a/fs_mgr/libdm/loop_control.cpp b/fs_mgr/libdm/loop_control.cpp
new file mode 100644
index 0000000..0beb1a6
--- /dev/null
+++ b/fs_mgr/libdm/loop_control.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 "libdm/loop_control.h"
+
+#include <fcntl.h>
+#include <linux/loop.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace dm {
+
+LoopControl::LoopControl() : control_fd_(-1) {
+    control_fd_.reset(TEMP_FAILURE_RETRY(open(kLoopControlDevice, O_RDWR | O_CLOEXEC)));
+    if (control_fd_ < 0) {
+        PLOG(ERROR) << "Failed to open loop-control";
+    }
+}
+
+bool LoopControl::Attach(int file_fd, std::string* loopdev) const {
+    if (!FindFreeLoopDevice(loopdev)) {
+        LOG(ERROR) << "Failed to attach, no free loop devices";
+        return false;
+    }
+
+    android::base::unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loopdev->c_str(), O_RDWR | O_CLOEXEC)));
+    if (loop_fd < 0) {
+        PLOG(ERROR) << "Failed to open: " << *loopdev;
+        return false;
+    }
+
+    int rc = ioctl(loop_fd, LOOP_SET_FD, file_fd);
+    if (rc < 0) {
+        PLOG(ERROR) << "Failed LOOP_SET_FD";
+        return false;
+    }
+    return true;
+}
+
+bool LoopControl::Detach(const std::string& loopdev) const {
+    if (loopdev.empty()) {
+        LOG(ERROR) << "Must provide a loop device";
+        return false;
+    }
+
+    android::base::unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loopdev.c_str(), O_RDWR | O_CLOEXEC)));
+    if (loop_fd < 0) {
+        PLOG(ERROR) << "Failed to open: " << loopdev;
+        return false;
+    }
+
+    int rc = ioctl(loop_fd, LOOP_CLR_FD, 0);
+    if (rc) {
+        PLOG(ERROR) << "Failed LOOP_CLR_FD for '" << loopdev << "'";
+        return false;
+    }
+    return true;
+}
+
+bool LoopControl::FindFreeLoopDevice(std::string* loopdev) const {
+    int rc = ioctl(control_fd_, LOOP_CTL_GET_FREE);
+    if (rc < 0) {
+        PLOG(ERROR) << "Failed to get free loop device";
+        return false;
+    }
+
+    // Ueventd on android creates all loop devices as /dev/block/loopX
+    // The total number of available devices is determined by 'loop.max_part'
+    // kernel command line argument.
+    *loopdev = ::android::base::StringPrintf("/dev/block/loop%d", rc);
+    return true;
+}
+
+LoopDevice::LoopDevice(int fd, bool auto_close) : fd_(fd), owns_fd_(auto_close) {
+    Init();
+}
+
+LoopDevice::LoopDevice(const std::string& path) : fd_(-1), owns_fd_(true) {
+    fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
+    if (fd_ < -1) {
+        PLOG(ERROR) << "open failed for " << path;
+        return;
+    }
+    Init();
+}
+
+LoopDevice::~LoopDevice() {
+    if (valid()) {
+        control_.Detach(device_);
+    }
+    if (!owns_fd_) {
+        (void)fd_.release();
+    }
+}
+
+void LoopDevice::Init() {
+    control_.Attach(fd_, &device_);
+}
+
+}  // namespace dm
+}  // namespace android
diff --git a/fs_mgr/libdm/loop_control_test.cpp b/fs_mgr/libdm/loop_control_test.cpp
new file mode 100644
index 0000000..08bdc00
--- /dev/null
+++ b/fs_mgr/libdm/loop_control_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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 "libdm/loop_control.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include "test_util.h"
+
+using namespace std;
+using namespace android::dm;
+using unique_fd = android::base::unique_fd;
+
+static unique_fd TempFile() {
+    // A loop device needs to be at least one sector to actually work, so fill
+    // up the file with a message.
+    unique_fd fd(CreateTempFile("temp", 0));
+    if (fd < 0) {
+        return {};
+    }
+    char buffer[] = "Hello";
+    for (size_t i = 0; i < 1000; i++) {
+        if (!android::base::WriteFully(fd, buffer, sizeof(buffer))) {
+            perror("write");
+            return {};
+        }
+    }
+    return fd;
+}
+
+TEST(libdm, LoopControl) {
+    unique_fd fd = TempFile();
+    ASSERT_GE(fd, 0);
+
+    LoopDevice loop(fd);
+    ASSERT_TRUE(loop.valid());
+
+    char buffer[6];
+    unique_fd loop_fd(open(loop.device().c_str(), O_RDWR));
+    ASSERT_GE(loop_fd, 0);
+    ASSERT_TRUE(android::base::ReadFully(loop_fd, buffer, sizeof(buffer)));
+    ASSERT_EQ(memcmp(buffer, "Hello", 6), 0);
+}
diff --git a/fs_mgr/libdm/test_util.cpp b/fs_mgr/libdm/test_util.cpp
new file mode 100644
index 0000000..307251c
--- /dev/null
+++ b/fs_mgr/libdm/test_util.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 <sys/types.h>
+#include <unistd.h>
+
+#include "test_util.h"
+
+namespace android {
+namespace dm {
+
+using unique_fd = android::base::unique_fd;
+
+// Create a temporary in-memory file. If size is non-zero, the file will be
+// created with a fixed size.
+unique_fd CreateTempFile(const std::string& name, size_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        return {};
+    }
+    if (size) {
+        if (ftruncate(fd, size) < 0) {
+            perror("ftruncate");
+            return {};
+        }
+        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            perror("fcntl");
+            return {};
+        }
+    }
+    return fd;
+}
+
+}  // namespace dm
+}  // namespace android
diff --git a/fs_mgr/libdm/test_util.h b/fs_mgr/libdm/test_util.h
new file mode 100644
index 0000000..96b051c
--- /dev/null
+++ b/fs_mgr/libdm/test_util.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBDM_TEST_UTILS_H_
+#define _LIBDM_TEST_UTILS_H_
+
+#include <android-base/unique_fd.h>
+#include <stddef.h>
+
+#include <string>
+
+namespace android {
+namespace dm {
+
+// Create a temporary in-memory file. If size is non-zero, the file will be
+// created with a fixed size.
+android::base::unique_fd CreateTempFile(const std::string& name, size_t size);
+
+}  // namespace dm
+}  // namespace android
+
+#endif  // _LIBDM_TEST_UTILS_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 e82c718..5e11c84 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -22,50 +22,142 @@
 #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 << "  table <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 << "Usage: dmctl create <name> [table-args]" << std::endl;
+        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)) {
+    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;
 }
 
@@ -86,7 +178,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 +243,56 @@
     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 int TableCmdHandler(int argc, char** argv) {
+    if (argc != 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    std::vector<DeviceMapper::TargetInfo> table;
+    if (!dm.GetTableStatus(argv[0], &table)) {
+        std::cerr << "Could not query table status of device \"" << argv[0] << "\"." << std::endl;
+        return -EINVAL;
+    }
+    std::cout << "Targets in the device-mapper table for " << argv[0] << ":" << std::endl;
+    for (const auto& target : table) {
+        std::cout << target.spec.sector_start << "-"
+                  << (target.spec.sector_start + target.spec.length) << ": "
+                  << target.spec.target_type;
+        if (!target.data.empty()) {
+            std::cout << ", " << target.data;
+        }
+        std::cout << 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},
+        {"table", TableCmdHandler},
+        // clang-format on
 };
 
 int main(int argc, char** argv) {
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index 61c8804..abb387c 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -234,11 +234,13 @@
     virtual int verify(uint32_t uid,
             const uint8_t *enrolled_password_handle, uint32_t enrolled_password_handle_length,
             const uint8_t *provided_password, uint32_t provided_password_length, bool *request_reenroll) {
-        uint8_t *auth_token;
+        uint8_t *auth_token = nullptr;
         uint32_t auth_token_length;
-        return verifyChallenge(uid, 0, enrolled_password_handle, enrolled_password_handle_length,
+        int ret = verifyChallenge(uid, 0, enrolled_password_handle, enrolled_password_handle_length,
                 provided_password, provided_password_length,
                 &auth_token, &auth_token_length, request_reenroll);
+        delete [] auth_token;
+        return ret;
     }
 
     virtual int verifyChallenge(uint32_t uid, uint64_t challenge,
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/init/tokenizer.cpp b/init/tokenizer.cpp
index f8d9b6b..bb143f1 100644
--- a/init/tokenizer.cpp
+++ b/init/tokenizer.cpp
@@ -85,15 +85,19 @@
                 goto textdone;
             case 'n':
                 *s++ = '\n';
+                x++;
                 break;
             case 'r':
                 *s++ = '\r';
+                x++;
                 break;
             case 't':
                 *s++ = '\t';
+                x++;
                 break;
             case '\\':
                 *s++ = '\\';
+                x++;
                 break;
             case '\r':
                     /* \ <cr> <lf> -> line continuation */
@@ -101,6 +105,7 @@
                     x++;
                     continue;
                 }
+                x++;
             case '\n':
                     /* \ <lf> -> line continuation */
                 state->line++;
diff --git a/liblog/include/android/log.h b/liblog/include/android/log.h
index 52cbe8b..ee9220d 100644
--- a/liblog/include/android/log.h
+++ b/liblog/include/android/log.h
@@ -115,16 +115,8 @@
  */
 int __android_log_print(int prio, const char* tag, const char* fmt, ...)
 #if defined(__GNUC__)
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-    __attribute__((__format__(gnu_printf, 3, 4)))
-#else
     __attribute__((__format__(printf, 3, 4)))
 #endif
-#else
-    __attribute__((__format__(printf, 3, 4)))
-#endif
-#endif
     ;
 
 /**
@@ -133,16 +125,8 @@
  */
 int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap)
 #if defined(__GNUC__)
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-    __attribute__((__format__(gnu_printf, 3, 0)))
-#else
     __attribute__((__format__(printf, 3, 0)))
 #endif
-#else
-    __attribute__((__format__(printf, 3, 0)))
-#endif
-#endif
     ;
 
 /**
@@ -164,16 +148,8 @@
                           ...)
 #if defined(__GNUC__)
     __attribute__((__noreturn__))
-#ifdef __USE_MINGW_ANSI_STDIO
-#if __USE_MINGW_ANSI_STDIO
-    __attribute__((__format__(gnu_printf, 3, 4)))
-#else
     __attribute__((__format__(printf, 3, 4)))
 #endif
-#else
-    __attribute__((__format__(printf, 3, 4)))
-#endif
-#endif
     ;
 
 #ifndef log_id_t_defined
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/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 35a3063..f0c66ec 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -239,12 +239,13 @@
     asprintf(&mParams[1], "INTERFACE=%s", ifname);
     asprintf(&mParams[2], "FLAGS=%u", ifaddr->ifa_flags);
     asprintf(&mParams[3], "SCOPE=%u", ifaddr->ifa_scope);
+    asprintf(&mParams[4], "IFINDEX=%u", ifaddr->ifa_index);
 
     if (cacheinfo) {
-        asprintf(&mParams[4], "PREFERRED=%u", cacheinfo->ifa_prefered);
-        asprintf(&mParams[5], "VALID=%u", cacheinfo->ifa_valid);
-        asprintf(&mParams[6], "CSTAMP=%u", cacheinfo->cstamp);
-        asprintf(&mParams[7], "TSTAMP=%u", cacheinfo->tstamp);
+        asprintf(&mParams[5], "PREFERRED=%u", cacheinfo->ifa_prefered);
+        asprintf(&mParams[6], "VALID=%u", cacheinfo->ifa_valid);
+        asprintf(&mParams[7], "CSTAMP=%u", cacheinfo->cstamp);
+        asprintf(&mParams[8], "TSTAMP=%u", cacheinfo->tstamp);
     }
 
     return true;
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 954a821..915cddb 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -211,7 +211,7 @@
       return false;
     }
 
-    if (HandleType(offset, phdr.p_type, *load_bias)) {
+    if (HandleType(offset, phdr.p_type)) {
       continue;
     }
 
diff --git a/libunwindstack/ElfInterfaceArm.cpp b/libunwindstack/ElfInterfaceArm.cpp
index 9b61599..a3244e8 100644
--- a/libunwindstack/ElfInterfaceArm.cpp
+++ b/libunwindstack/ElfInterfaceArm.cpp
@@ -87,20 +87,22 @@
 #define PT_ARM_EXIDX 0x70000001
 #endif
 
-bool ElfInterfaceArm::HandleType(uint64_t offset, uint32_t type, uint64_t load_bias) {
+bool ElfInterfaceArm::HandleType(uint64_t offset, uint32_t type) {
   if (type != PT_ARM_EXIDX) {
     return false;
   }
 
   Elf32_Phdr phdr;
-  if (!memory_->ReadField(offset, &phdr, &phdr.p_vaddr, sizeof(phdr.p_vaddr))) {
+  if (!memory_->ReadFully(offset, &phdr, sizeof(phdr))) {
     return true;
   }
-  if (!memory_->ReadField(offset, &phdr, &phdr.p_memsz, sizeof(phdr.p_memsz))) {
-    return true;
-  }
-  start_offset_ = phdr.p_vaddr - load_bias;
-  total_entries_ = phdr.p_memsz / 8;
+
+  // The offset already takes into account the load bias.
+  start_offset_ = phdr.p_offset;
+
+  // Always use filesz instead of memsz. In most cases they are the same,
+  // but some shared libraries wind up setting one correctly and not the other.
+  total_entries_ = phdr.p_filesz / 8;
   return true;
 }
 
diff --git a/libunwindstack/ElfInterfaceArm.h b/libunwindstack/ElfInterfaceArm.h
index 18efb6c..3bee9cf 100644
--- a/libunwindstack/ElfInterfaceArm.h
+++ b/libunwindstack/ElfInterfaceArm.h
@@ -70,7 +70,7 @@
 
   bool FindEntry(uint32_t pc, uint64_t* entry_offset);
 
-  bool HandleType(uint64_t offset, uint32_t type, uint64_t load_bias) override;
+  bool HandleType(uint64_t offset, uint32_t type) override;
 
   bool Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) override;
 
diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h
index 4d25c40..0c588da 100644
--- a/libunwindstack/include/unwindstack/ElfInterface.h
+++ b/libunwindstack/include/unwindstack/ElfInterface.h
@@ -118,7 +118,7 @@
   template <typename SymType>
   bool GetGlobalVariableWithTemplate(const std::string& name, uint64_t* memory_address);
 
-  virtual bool HandleType(uint64_t, uint32_t, uint64_t) { return false; }
+  virtual bool HandleType(uint64_t, uint32_t) { return false; }
 
   template <typename EhdrType>
   static void GetMaxSizeWithTemplate(Memory* memory, uint64_t* size);
diff --git a/libunwindstack/tests/ElfInterfaceArmTest.cpp b/libunwindstack/tests/ElfInterfaceArmTest.cpp
index 5f1c2ac..a8bb4aa 100644
--- a/libunwindstack/tests/ElfInterfaceArmTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceArmTest.cpp
@@ -245,56 +245,41 @@
 TEST_F(ElfInterfaceArmTest, HandleType_not_arm_exidx) {
   ElfInterfaceArmFake interface(&memory_);
 
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_NULL, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOAD, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_DYNAMIC, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_INTERP, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_NOTE, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_SHLIB, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_PHDR, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_TLS, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOOS, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_HIOS, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOPROC, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_HIPROC, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_GNU_EH_FRAME, 0));
-  ASSERT_FALSE(interface.HandleType(0x1000, PT_GNU_STACK, 0));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_NULL));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOAD));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_DYNAMIC));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_INTERP));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_NOTE));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_SHLIB));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_PHDR));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_TLS));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOOS));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_HIOS));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_LOPROC));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_HIPROC));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_GNU_EH_FRAME));
+  ASSERT_FALSE(interface.HandleType(0x1000, PT_GNU_STACK));
 }
 
 TEST_F(ElfInterfaceArmTest, HandleType_arm_exidx) {
   ElfInterfaceArmFake interface(&memory_);
 
-  Elf32_Phdr phdr;
+  Elf32_Phdr phdr = {};
   interface.FakeSetStartOffset(0x1000);
   interface.FakeSetTotalEntries(100);
-  phdr.p_vaddr = 0x2000;
-  phdr.p_memsz = 0xa00;
+  phdr.p_offset = 0x2000;
+  phdr.p_filesz = 0xa00;
 
   // Verify that if reads fail, we don't set the values but still get true.
-  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001, 0));
-  ASSERT_EQ(0x1000U, interface.start_offset());
-  ASSERT_EQ(100U, interface.total_entries());
-
-  // Verify that if the second read fails, we still don't set the values.
-  memory_.SetData32(
-      0x1000 + reinterpret_cast<uint64_t>(&phdr.p_vaddr) - reinterpret_cast<uint64_t>(&phdr),
-      phdr.p_vaddr);
-  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001, 0));
+  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001));
   ASSERT_EQ(0x1000U, interface.start_offset());
   ASSERT_EQ(100U, interface.total_entries());
 
   // Everything is correct and present.
-  memory_.SetData32(
-      0x1000 + reinterpret_cast<uint64_t>(&phdr.p_memsz) - reinterpret_cast<uint64_t>(&phdr),
-      phdr.p_memsz);
-  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001, 0));
+  memory_.SetMemory(0x1000, &phdr, sizeof(phdr));
+  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001));
   ASSERT_EQ(0x2000U, interface.start_offset());
   ASSERT_EQ(320U, interface.total_entries());
-
-  // Non-zero load bias.
-  ASSERT_TRUE(interface.HandleType(0x1000, 0x70000001, 0x1000));
-  ASSERT_EQ(0x1000U, interface.start_offset());
-  ASSERT_EQ(320U, interface.total_entries());
 }
 
 TEST_F(ElfInterfaceArmTest, StepExidx) {
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index 4008e9b..487d39c 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -116,8 +116,7 @@
 template <typename Sym>
 void ElfInterfaceTest::InitSym(uint64_t offset, uint32_t value, uint32_t size, uint32_t name_offset,
                                uint64_t sym_offset, const char* name) {
-  Sym sym;
-  memset(&sym, 0, sizeof(sym));
+  Sym sym = {};
   sym.st_info = STT_FUNC;
   sym.st_value = value;
   sym.st_size = size;
@@ -132,15 +131,13 @@
 void ElfInterfaceTest::SinglePtLoad() {
   std::unique_ptr<ElfInterface> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 1;
   ehdr.e_phentsize = sizeof(Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -172,15 +169,13 @@
 void ElfInterfaceTest::MultipleExecutablePtLoads() {
   std::unique_ptr<ElfInterface> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 3;
   ehdr.e_phentsize = sizeof(Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -241,15 +236,13 @@
 void ElfInterfaceTest::MultipleExecutablePtLoadsIncrementsNotSizeOfPhdr() {
   std::unique_ptr<ElfInterface> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 3;
   ehdr.e_phentsize = sizeof(Phdr) + 100;
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -312,15 +305,13 @@
 void ElfInterfaceTest::NonExecutablePtLoads() {
   std::unique_ptr<ElfInterface> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 3;
   ehdr.e_phentsize = sizeof(Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -371,17 +362,15 @@
 void ElfInterfaceTest::ManyPhdrs() {
   std::unique_ptr<ElfInterface> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 7;
   ehdr.e_phentsize = sizeof(Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Phdr phdr;
   uint64_t phdr_offset = 0x100;
 
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -444,18 +433,16 @@
 TEST_F(ElfInterfaceTest, elf32_arm) {
   ElfInterfaceArm elf_arm(&memory_);
 
-  Elf32_Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Elf32_Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 1;
   ehdr.e_phentsize = sizeof(Elf32_Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Elf32_Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Elf32_Phdr phdr = {};
   phdr.p_type = PT_ARM_EXIDX;
-  phdr.p_vaddr = 0x2000;
-  phdr.p_memsz = 16;
+  phdr.p_offset = 0x2000;
+  phdr.p_filesz = 16;
   memory_.SetMemory(0x100, &phdr, sizeof(phdr));
 
   // Add arm exidx entries.
@@ -480,8 +467,7 @@
 
 template <typename Ehdr, typename Phdr, typename Shdr, typename Dyn>
 void ElfInterfaceTest::SonameInit(SonameTestEnum test_type) {
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_shoff = 0x200;
   ehdr.e_shnum = 2;
   ehdr.e_shentsize = sizeof(Shdr);
@@ -490,8 +476,7 @@
   ehdr.e_phentsize = sizeof(Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Shdr shdr;
-  memset(&shdr, 0, sizeof(shdr));
+  Shdr shdr = {};
   shdr.sh_type = SHT_STRTAB;
   if (test_type == SONAME_MISSING_MAP) {
     shdr.sh_addr = 0x20100;
@@ -501,8 +486,7 @@
   shdr.sh_offset = 0x10000;
   memory_.SetMemory(0x200 + sizeof(shdr), &shdr, sizeof(shdr));
 
-  Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Phdr phdr = {};
   phdr.p_type = PT_DYNAMIC;
   phdr.p_offset = 0x2000;
   phdr.p_memsz = sizeof(Dyn) * 3;
@@ -748,8 +732,7 @@
 void ElfInterfaceTest::InitSectionHeadersMalformed() {
   std::unique_ptr<ElfInterfaceType> elf(new ElfInterfaceType(&memory_));
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_shoff = 0x1000;
   ehdr.e_shnum = 10;
   ehdr.e_shentsize = sizeof(Shdr);
@@ -774,8 +757,7 @@
 
   uint64_t offset = 0x1000;
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_shoff = offset;
   ehdr.e_shnum = 10;
   ehdr.e_shentsize = entry_size;
@@ -783,8 +765,7 @@
 
   offset += ehdr.e_shentsize;
 
-  Shdr shdr;
-  memset(&shdr, 0, sizeof(shdr));
+  Shdr shdr = {};
   shdr.sh_type = SHT_SYMTAB;
   shdr.sh_link = 4;
   shdr.sh_addr = 0x5000;
@@ -863,8 +844,7 @@
 
   uint64_t offset = 0x2000;
 
-  Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Ehdr ehdr = {};
   ehdr.e_shoff = offset;
   ehdr.e_shnum = 10;
   ehdr.e_shentsize = sizeof(Shdr);
@@ -873,8 +853,7 @@
 
   offset += ehdr.e_shentsize;
 
-  Shdr shdr;
-  memset(&shdr, 0, sizeof(shdr));
+  Shdr shdr = {};
   shdr.sh_type = SHT_PROGBITS;
   shdr.sh_link = 2;
   shdr.sh_name = 0x200;
@@ -956,15 +935,13 @@
 TEST_F(ElfInterfaceTest, is_valid_pc_from_pt_load) {
   std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
 
-  Elf32_Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Elf32_Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 1;
   ehdr.e_phentsize = sizeof(Elf32_Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Elf32_Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Elf32_Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0;
   phdr.p_memsz = 0x10000;
@@ -984,15 +961,13 @@
 TEST_F(ElfInterfaceTest, is_valid_pc_from_pt_load_non_zero_load_bias) {
   std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
 
-  Elf32_Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Elf32_Ehdr ehdr = {};
   ehdr.e_phoff = 0x100;
   ehdr.e_phnum = 1;
   ehdr.e_phentsize = sizeof(Elf32_Phdr);
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Elf32_Phdr phdr;
-  memset(&phdr, 0, sizeof(phdr));
+  Elf32_Phdr phdr = {};
   phdr.p_type = PT_LOAD;
   phdr.p_vaddr = 0x2000;
   phdr.p_memsz = 0x10000;
@@ -1017,16 +992,14 @@
 
   uint64_t sh_offset = 0x100;
 
-  Elf32_Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Elf32_Ehdr ehdr = {};
   ehdr.e_shstrndx = 1;
   ehdr.e_shoff = sh_offset;
   ehdr.e_shentsize = sizeof(Elf32_Shdr);
   ehdr.e_shnum = 3;
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Elf32_Shdr shdr;
-  memset(&shdr, 0, sizeof(shdr));
+  Elf32_Shdr shdr = {};
   shdr.sh_type = SHT_NULL;
   memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
 
@@ -1080,16 +1053,14 @@
 
   uint64_t sh_offset = 0x100;
 
-  Elf32_Ehdr ehdr;
-  memset(&ehdr, 0, sizeof(ehdr));
+  Elf32_Ehdr ehdr = {};
   ehdr.e_shstrndx = 1;
   ehdr.e_shoff = sh_offset;
   ehdr.e_shentsize = sizeof(Elf32_Shdr);
   ehdr.e_shnum = 3;
   memory_.SetMemory(0, &ehdr, sizeof(ehdr));
 
-  Elf32_Shdr shdr;
-  memset(&shdr, 0, sizeof(shdr));
+  Elf32_Shdr shdr = {};
   shdr.sh_type = SHT_NULL;
   memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
 
diff --git a/run-as/Android.bp b/run-as/Android.bp
new file mode 100644
index 0000000..840a43c
--- /dev/null
+++ b/run-as/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+cc_binary {
+    name: "run-as",
+    srcs: [
+        "run-as.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libselinux",
+        "libpackagelistparser",
+        "libminijail",
+    ],
+}
diff --git a/run-as/Android.mk b/run-as/Android.mk
deleted file mode 100644
index 7111fbe..0000000
--- a/run-as/Android.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := run-as
-LOCAL_SHARED_LIBRARIES := libselinux libpackagelistparser libminijail
-LOCAL_SRC_FILES := run-as.cpp
-include $(BUILD_EXECUTABLE)
diff --git a/run-as/run-as.cpp b/run-as/run-as.cpp
index b27cfad..d005ecf 100644
--- a/run-as/run-as.cpp
+++ b/run-as/run-as.cpp
@@ -28,6 +28,7 @@
 #include <libminijail.h>
 #include <scoped_minijail.h>
 
+#include <android-base/properties.h>
 #include <packagelistparser/packagelistparser.h>
 #include <private/android_filesystem_config.h>
 #include <selinux/android.h>
@@ -40,6 +41,7 @@
 //  The 'run-as' binary is installed with CAP_SETUID and CAP_SETGID file
 //  capabilities, but will check the following:
 //
+//  - that the ro.boot.disable_runas property is not set
 //  - that it is invoked from the 'shell' or 'root' user (abort otherwise)
 //  - that '<package-name>' is the name of an installed and debuggable package
 //  - that the package's data directory is well-formed
@@ -139,6 +141,12 @@
     error(1, 0, "only 'shell' or 'root' users can run this program");
   }
 
+  // Some devices can disable running run-as, such as Chrome OS when running in
+  // non-developer mode.
+  if (android::base::GetBoolProperty("ro.boot.disable_runas", false)) {
+      error(1, 0, "run-as is disabled from the kernel commandline");
+  }
+
   char* pkgname = argv[1];
   int cmd_argv_offset = 2;