Snap for 5798008 from bee55a0d7511aaba52626f223bf0a93912241fcd to sdk-release

Change-Id: Icb2fb29adc2775cda70e1227cf606612519fa98c
diff --git a/Android.bp b/Android.bp
index ba22afc..da02c78 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,7 +58,6 @@
         "daemon.cpp",
         "gsi_service.cpp",
         "gsi_installer.cpp",
-        "utility.cpp",
     ],
     required: [
         "mke2fs",
@@ -86,11 +85,7 @@
 
 aidl_interface {
     name: "gsi_aidl_interface",
-    srcs: [
-        "aidl/android/gsi/GsiInstallParams.aidl",
-        "aidl/android/gsi/GsiProgress.aidl",
-        "aidl/android/gsi/IGsiService.aidl",
-    ],
+    srcs: [":gsiservice_aidl"],
     local_include_dir: "aidl",
     backend: {
         ndk: {
@@ -104,6 +99,9 @@
     srcs: [
         "aidl/android/gsi/GsiInstallParams.aidl",
         "aidl/android/gsi/GsiProgress.aidl",
+        "aidl/android/gsi/IImageService.aidl",
+        "aidl/android/gsi/IGsid.aidl",
         "aidl/android/gsi/IGsiService.aidl",
+        "aidl/android/gsi/MappedImage.aidl",
     ],
 }
diff --git a/aidl/android/gsi/IGsiService.aidl b/aidl/android/gsi/IGsiService.aidl
index 4ffdf62..6ca0af5 100644
--- a/aidl/android/gsi/IGsiService.aidl
+++ b/aidl/android/gsi/IGsiService.aidl
@@ -18,6 +18,7 @@
 
 import android.gsi.GsiInstallParams;
 import android.gsi.GsiProgress;
+import android.gsi.IImageService;
 import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
@@ -181,4 +182,13 @@
      * @return              0 on success, an error code on failure.
      */
     int wipeGsiUserdata();
+
+    /**
+     * Open a handle to an IImageService for the given metadata and data storage paths.
+     *
+     * @param prefix        A prefix used to organize images. The data path will become
+     *                      /data/gsi/{prefix} and the metadata path will become
+     *                      /metadata/gsi/{prefix}.
+     */
+    IImageService openImageService(@utf8InCpp String prefix);
 }
diff --git a/aidl/android/gsi/IGsid.aidl b/aidl/android/gsi/IGsid.aidl
new file mode 100644
index 0000000..0c1a7b2
--- /dev/null
+++ b/aidl/android/gsi/IGsid.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.gsi;
+
+import android.gsi.IGsiService;
+
+/** {@hide} */
+interface IGsid {
+    // Acquire an IGsiService client. gsid automatically shuts down when the
+    // last client is dropped. To start the daemon:
+    //
+    //  1. Check if the "init.svc.gsid" property is "running". If not, continue.
+    //  2. Set the "ctl.start" property to "gsid".
+    //  3. Wait for "init.svc.gsid" to be "running".
+    IGsiService getClient();
+}
diff --git a/aidl/android/gsi/IImageService.aidl b/aidl/android/gsi/IImageService.aidl
new file mode 100644
index 0000000..87ba84a
--- /dev/null
+++ b/aidl/android/gsi/IImageService.aidl
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.gsi;
+
+import android.gsi.MappedImage;
+
+/** {@hide} */
+interface IImageService {
+    /* These flags match fiemap::ImageManager::CreateBackingImage. */
+    const int CREATE_IMAGE_DEFAULT = 0x0;
+    const int CREATE_IMAGE_READONLY = 0x1;
+    const int CREATE_IMAGE_ZERO_FILL = 0x2;
+
+    /**
+     * Create an image that can be mapped as a block device.
+     *
+     * This call will fail if running a GSI.
+     *
+     * @param name          Image name. If the image already exists, the call will fail.
+     * @param size          Image size, in bytes. If too large, or not enough space is
+     *                      free, the call will fail.
+     * @param readonly      If readonly, MapBackingImage() will configure the device as
+     *                      readonly.
+     * @return              True on success, false otherwise.
+     */
+    void createBackingImage(@utf8InCpp String name, long size, int flags);
+
+    /**
+     * Delete an image created with createBackingImage.
+     *
+     * @param name          Image name as passed to createBackingImage().
+     * @return              True on success, false otherwise.
+     */
+    void deleteBackingImage(@utf8InCpp String name);
+
+    /**
+     * Map an image, created with createBackingImage, such that it is accessible as a
+     * block device.
+     *
+     * @param name          Image name as passed to createBackingImage().
+     * @param timeout_ms    Time to wait for a valid mapping, in milliseconds. This must be more
+     *                      than zero; 10 seconds is recommended.
+     * @param mapping       Information about the newly mapped block device.
+     */
+    void mapImageDevice(@utf8InCpp String name, int timeout_ms, out MappedImage mapping);
+
+    /**
+     * Unmap a block device previously mapped with mapBackingImage. This step is necessary before
+     * calling deleteBackingImage.
+     *
+     * @param name          Image name as passed to createBackingImage().
+     */
+    void unmapImageDevice(@utf8InCpp String name);
+
+    /**
+     * Returns whether or not a backing image exists.
+     *
+     * @param name          Image name as passed to createBackingImage().
+     */
+    boolean backingImageExists(@utf8InCpp String name);
+
+    /**
+     * Returns whether or not the named image is mapped.
+     *
+     * @param name          Image name as passed to createBackingImage().
+     */
+    boolean isImageMapped(@utf8InCpp String name);
+}
diff --git a/aidl/android/gsi/MappedImage.aidl b/aidl/android/gsi/MappedImage.aidl
new file mode 100644
index 0000000..aa5af0d
--- /dev/null
+++ b/aidl/android/gsi/MappedImage.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.gsi;
+
+/** {@hide} */
+parcelable MappedImage {
+    /* Path to the block device. */
+    @utf8InCpp String path;
+}
diff --git a/daemon.cpp b/daemon.cpp
index 0ea1cb5..3fa7b3f 100644
--- a/daemon.cpp
+++ b/daemon.cpp
@@ -14,15 +14,17 @@
 // limitations under the License.
 //
 
-#include "gsi_service.h"
-
 #include <getopt.h>
 
 #include <string>
 
 #include <android-base/logging.h>
+#include <binder/BinderService.h>
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
+#include <libgsi/libgsi.h>
+
+#include "gsi_service.h"
 
 using android::ProcessState;
 using android::sp;
@@ -36,7 +38,7 @@
         exit(0);
     }
 
-    android::gsi::GsiService::Register();
+    android::gsi::Gsid::Register();
     {
         sp<ProcessState> ps(ProcessState::self());
         ps->startThreadPool();
diff --git a/file_paths.h b/file_paths.h
index a772bce..8d1ed0a 100644
--- a/file_paths.h
+++ b/file_paths.h
@@ -19,19 +19,20 @@
 namespace android {
 namespace gsi {
 
-static constexpr char kDefaultGsiImageFolder[] = "/data/gsi/dsu/";
+static constexpr char kDefaultDsuImageFolder[] = "/data/gsi/dsu/";
 static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";
 
-static constexpr char kGsiLpMetadataFile[] = "/metadata/gsi/dsu/lp_metadata";
-static constexpr char kGsiOneShotBootFile[] = "/metadata/gsi/dsu/one_shot_boot";
-static constexpr char kGsiInstallDirFile[] = "/metadata/gsi/dsu/install_dir";
+static constexpr char kDsuMetadataDir[] = "/metadata/gsi/dsu";
+static constexpr char kDsuLpMetadataFile[] = "/metadata/gsi/dsu/lp_metadata";
+static constexpr char kDsuOneShotBootFile[] = "/metadata/gsi/dsu/one_shot_boot";
+static constexpr char kDsuInstallDirFile[] = "/metadata/gsi/dsu/install_dir";
 
 // This file can contain the following values:
 //   [int]      - boot attempt counter, starting from 0
 //   "ok"       - boot was marked successful
 //   "disabled" - boot into GSI no longer allowed
 //   "wipe"     - boot into GSI not allowed; next reboot will delete gsi
-static constexpr char kGsiInstallStatusFile[] = "/metadata/gsi/dsu/install_status";
+static constexpr char kDsuInstallStatusFile[] = "/metadata/gsi/dsu/install_status";
 
 }  // namespace gsi
 }  // namespace android
diff --git a/gsi_installer.cpp b/gsi_installer.cpp
index dcca310..e08e642 100644
--- a/gsi_installer.cpp
+++ b/gsi_installer.cpp
@@ -29,7 +29,6 @@
 #include "file_paths.h"
 #include "gsi_service.h"
 #include "libgsi_private.h"
-#include "utility.h"
 
 namespace android {
 namespace gsi {
@@ -43,12 +42,8 @@
 // The default size of userdata.img for GSI.
 // We are looking for /data to have atleast 40% free space
 static constexpr uint32_t kMinimumFreeSpaceThreshold = 40;
-// We determine the fragmentation by making sure the files
-// we create don't have more than 16 extents.
-static constexpr uint32_t kMaximumExtents = 512;
 // Default userdata image size.
 static constexpr int64_t kDefaultUserdataSize = int64_t(2) * 1024 * 1024 * 1024;
-static constexpr std::chrono::milliseconds kDmTimeout = 5000ms;
 
 GsiInstaller::GsiInstaller(GsiService* service, const GsiInstallParams& params)
     : service_(service),
@@ -56,16 +51,17 @@
       gsi_size_(params.gsiSize),
       wipe_userdata_(params.wipeUserdata) {
     userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize;
-    userdata_gsi_path_ = GetImagePath("userdata_gsi");
-    system_gsi_path_ = GetImagePath("system_gsi");
+    images_ = ImageManager::Open(kDsuMetadataDir, install_dir_);
 
     // Only rm userdata_gsi if one didn't already exist.
-    wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK);
+    if (wipe_userdata_ || !images_->BackingImageExists("userdata_gsi")) {
+        wipe_userdata_on_failure_ = true;
+    }
 }
 
 GsiInstaller::GsiInstaller(GsiService* service, const std::string& install_dir)
     : service_(service), install_dir_(install_dir) {
-    system_gsi_path_ = GetImagePath("system_gsi");
+    images_ = ImageManager::Open(kDsuMetadataDir, install_dir_);
 
     // The install already exists, so always mark it as succeeded.
     succeeded_ = true;
@@ -74,21 +70,28 @@
 GsiInstaller::~GsiInstaller() {
     if (!succeeded_) {
         // Close open handles before we remove files.
-        system_writer_ = nullptr;
-        partitions_.clear();
-        PostInstallCleanup();
+        system_device_ = nullptr;
+        PostInstallCleanup(images_.get());
 
         GsiService::RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_);
     }
 }
 
 void GsiInstaller::PostInstallCleanup() {
-    const auto& dm = DeviceMapper::Instance();
-    if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) {
-        DestroyLogicalPartition("userdata_gsi");
+    auto manager = ImageManager::Open(kDsuMetadataDir, GsiService::GetInstalledImageDir());
+    if (!manager) {
+        LOG(ERROR) << "Could not open image manager";
+        return;
     }
-    if (dm.GetState("system_gsi") != DmDeviceState::INVALID) {
-        DestroyLogicalPartition("system_gsi");
+    return PostInstallCleanup(manager.get());
+}
+
+void GsiInstaller::PostInstallCleanup(ImageManager* manager) {
+    if (manager->IsImageMapped("userdata_gsi")) {
+        manager->UnmapImageDevice("userdata_gsi");
+    }
+    if (manager->IsImageMapped("system_gsi")) {
+        manager->UnmapImageDevice("system_gsi");
     }
 }
 
@@ -99,16 +102,13 @@
     if (int status = PreallocateFiles()) {
         return status;
     }
-    if (int status = DetermineReadWriteMethod()) {
-        return status;
-    }
     if (!FormatUserdata()) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
     // Map system_gsi so we can write to it.
-    system_writer_ = OpenPartition("system_gsi");
-    if (!system_writer_) {
+    system_device_ = OpenPartition("system_gsi");
+    if (!system_device_) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
@@ -117,32 +117,11 @@
     return IGsiService::INSTALL_OK;
 }
 
-int GsiInstaller::DetermineReadWriteMethod() {
-    // If there is a device-mapper node wrapping the block device, then we're
-    // able to create another node around it; the dm layer does not carry the
-    // exclusion lock down the stack when a mount occurs.
-    //
-    // If there is no intermediate device-mapper node, then partitions cannot be
-    // opened writable due to sepolicy and exclusivity of having a mounted
-    // filesystem. This should only happen on devices with no encryption, or
-    // devices with FBE and no metadata encryption. For these cases it suffices
-    // to perform normal file writes to /data/gsi (which is unencrypted).
-    std::string block_device;
-    if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device,
-                                             &can_use_devicemapper_)) {
-        return IGsiService::INSTALL_ERROR_GENERIC;
-    }
-    if (install_dir_ != kDefaultGsiImageFolder && can_use_devicemapper_) {
-        // Never use device-mapper on external media. We don't support adopted
-        // storage yet, and accidentally using device-mapper could be dangerous
-        // as we hardcode the userdata device as backing storage.
-        LOG(ERROR) << "unexpected device-mapper node used to mount external media";
-        return IGsiService::INSTALL_ERROR_GENERIC;
-    }
-    return IGsiService::INSTALL_OK;
-}
-
 int GsiInstaller::PerformSanityChecks() {
+    if (!images_) {
+        LOG(ERROR) << "unable to create image manager";
+        return IGsiService::INSTALL_ERROR_GENERIC;
+    }
     if (gsi_size_ < 0) {
         LOG(ERROR) << "image size " << gsi_size_ << " is negative";
         return IGsiService::INSTALL_ERROR_GENERIC;
@@ -179,11 +158,9 @@
 
 int GsiInstaller::PreallocateFiles() {
     if (wipe_userdata_) {
-        SplitFiemap::RemoveSplitFiles(userdata_gsi_path_);
+        images_->DeleteBackingImage("userdata_gsi");
     }
-    SplitFiemap::RemoveSplitFiles(system_gsi_path_);
-
-    // TODO: trigger GC from fiemap writer.
+    images_->DeleteBackingImage("system_gsi");
 
     // Create fallocated files.
     if (int status = PreallocateUserdata()) {
@@ -193,161 +170,48 @@
         return status;
     }
 
-    // Save the extent information in liblp.
-    metadata_ = CreateMetadata();
-    if (!metadata_) {
-        return IGsiService::INSTALL_ERROR_GENERIC;
-    }
-
     service_->UpdateProgress(IGsiService::STATUS_COMPLETE, 0);
     return IGsiService::INSTALL_OK;
 }
 
 int GsiInstaller::PreallocateUserdata() {
-    int error;
-    std::unique_ptr<SplitFiemap> userdata_image;
-    if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) {
+    if (wipe_userdata_ || !images_->BackingImageExists("userdata_gsi")) {
         service_->StartAsyncOperation("create userdata", userdata_size_);
-        userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error);
-        if (!userdata_image) {
-            LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_;
-            return error;
+        if (!CreateImage("userdata_gsi", userdata_size_, false)) {
+            LOG(ERROR) << "Could not create userdata image";
+            return IGsiService::INSTALL_ERROR_GENERIC;
         }
+
         // Signal that we need to reformat userdata.
         wipe_userdata_ = true;
-    } else {
-        userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error);
-        if (!userdata_image) {
-            LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_;
-            return error;
-        }
-        if (userdata_size_ && userdata_image->size() < userdata_size_) {
-            // :TODO: need to fallocate more blocks and resizefs.
-        }
-        userdata_size_ = userdata_image->size();
     }
-
-    userdata_block_size_ = userdata_image->block_size();
-
-    Image image = {
-            .writer = std::move(userdata_image),
-            .actual_size = userdata_size_,
-    };
-    partitions_.emplace(std::make_pair("userdata_gsi", std::move(image)));
     return IGsiService::INSTALL_OK;
 }
 
 int GsiInstaller::PreallocateSystem() {
     service_->StartAsyncOperation("create system", gsi_size_);
 
-    int error;
-    auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error);
-    if (!system_image) {
-        return error;
+    if (!CreateImage("system_gsi", gsi_size_, true)) {
+        return IGsiService::INSTALL_ERROR_GENERIC;
     }
-
-    system_block_size_ = system_image->block_size();
-
-    Image image = {
-            .writer = std::move(system_image),
-            .actual_size = gsi_size_,
-    };
-    partitions_.emplace(std::make_pair("system_gsi", std::move(image)));
     return IGsiService::INSTALL_OK;
 }
 
-std::unique_ptr<SplitFiemap> GsiInstaller::CreateFiemapWriter(const std::string& path,
-                                                              uint64_t size, int* error) {
-    bool create = (size != 0);
-
-    std::function<bool(uint64_t, uint64_t)> progress;
-    if (create) {
-        progress = [this](uint64_t bytes, uint64_t /* total */) -> bool {
-            service_->UpdateProgress(IGsiService::STATUS_WORKING, bytes);
-            if (service_->should_abort()) return false;
-            return true;
-        };
+bool GsiInstaller::CreateImage(const std::string& name, uint64_t size, bool readonly) {
+    auto progress = [this](uint64_t bytes, uint64_t /* total */) -> bool {
+        service_->UpdateProgress(IGsiService::STATUS_WORKING, bytes);
+        if (service_->should_abort()) return false;
+        return true;
+    };
+    int flags = ImageManager::CREATE_IMAGE_DEFAULT;
+    if (readonly) {
+        flags |= ImageManager::CREATE_IMAGE_READONLY;
     }
-
-    std::unique_ptr<SplitFiemap> file;
-    if (!size) {
-        file = SplitFiemap::Open(path);
-    } else {
-        file = SplitFiemap::Create(path, size, 0, std::move(progress));
-    }
-    if (!file) {
-        LOG(ERROR) << "failed to create or open " << path;
-        *error = IGsiService::INSTALL_ERROR_GENERIC;
-        return nullptr;
-    }
-
-    uint64_t extents = file->extents().size();
-    if (extents > kMaximumExtents) {
-        LOG(ERROR) << "file " << path << " has too many extents: " << extents;
-        *error = IGsiService::INSTALL_ERROR_FILE_SYSTEM_CLUTTERED;
-        return nullptr;
-    }
-    return file;
+    return images_->CreateBackingImage(name, size, flags, std::move(progress));
 }
 
-// Write data through an fd.
-class FdWriter final : public GsiInstaller::WriteHelper {
-  public:
-    FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {}
-
-    bool Write(const void* data, uint64_t bytes) override {
-        return android::base::WriteFully(fd_, data, bytes);
-    }
-    bool Flush() override {
-        if (fsync(fd_)) {
-            PLOG(ERROR) << "fsync failed: " << path_;
-            return false;
-        }
-        return true;
-    }
-    uint64_t Size() override { return get_block_device_size(fd_); }
-
-  private:
-    std::string path_;
-    unique_fd fd_;
-};
-
-// Write data through a SplitFiemap.
-class SplitFiemapWriter final : public GsiInstaller::WriteHelper {
-  public:
-    explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {}
-
-    bool Write(const void* data, uint64_t bytes) override { return writer_->Write(data, bytes); }
-    bool Flush() override { return writer_->Flush(); }
-    uint64_t Size() override { return writer_->size(); }
-
-  private:
-    SplitFiemap* writer_;
-};
-
-std::unique_ptr<GsiInstaller::WriteHelper> GsiInstaller::OpenPartition(const std::string& name) {
-    if (can_use_devicemapper_) {
-        std::string path;
-        if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout,
-                                    &path)) {
-            LOG(ERROR) << "Error creating device-mapper node for " << name;
-            return {};
-        }
-
-        static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
-        unique_fd fd(open(path.c_str(), kOpenFlags));
-        if (fd < 0) {
-            PLOG(ERROR) << "could not open " << path;
-        }
-        return std::make_unique<FdWriter>(GetImagePath(name), std::move(fd));
-    }
-
-    auto iter = partitions_.find(name);
-    if (iter == partitions_.end()) {
-        LOG(ERROR) << "could not find partition " << name;
-        return {};
-    }
-    return std::make_unique<SplitFiemapWriter>(iter->second.writer.get());
+std::unique_ptr<MappedDevice> GsiInstaller::OpenPartition(const std::string& name) {
+    return MappedDevice::Open(images_.get(), 10s, name);
 }
 
 bool GsiInstaller::CommitGsiChunk(int stream_fd, int64_t bytes) {
@@ -358,13 +222,13 @@
         return false;
     }
 
-    auto buffer = std::make_unique<char[]>(system_block_size_);
+    static const size_t kBlockSize = 4096;
+    auto buffer = std::make_unique<char[]>(kBlockSize);
 
     int progress = -1;
     uint64_t remaining = bytes;
     while (remaining) {
-        // :TODO: check file pin status!
-        size_t max_to_read = std::min(system_block_size_, remaining);
+        size_t max_to_read = std::min(static_cast<uint64_t>(kBlockSize), remaining);
         ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read));
         if (rv < 0) {
             PLOG(ERROR) << "read gsi chunk";
@@ -402,7 +266,7 @@
     if (service_->should_abort()) {
         return false;
     }
-    if (!system_writer_->Write(data, bytes)) {
+    if (!android::base::WriteFully(system_device_->fd(), data, bytes)) {
         PLOG(ERROR) << "write failed";
         return false;
     }
@@ -412,13 +276,13 @@
 
 bool GsiInstaller::SetBootMode(bool one_shot) {
     if (one_shot) {
-        if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) {
-            PLOG(ERROR) << "write " << kGsiOneShotBootFile;
+        if (!android::base::WriteStringToFile("1", kDsuOneShotBootFile)) {
+            PLOG(ERROR) << "write " << kDsuOneShotBootFile;
             return false;
         }
-    } else if (!access(kGsiOneShotBootFile, F_OK)) {
+    } else if (!access(kDsuOneShotBootFile, F_OK)) {
         std::string error;
-        if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) {
+        if (!android::base::RemoveFileIfExists(kDsuOneShotBootFile, &error)) {
             LOG(ERROR) << error;
             return false;
         }
@@ -426,157 +290,29 @@
     return true;
 }
 
-std::string GsiInstaller::GetImagePath(const std::string& name) {
-    return GsiService::GetImagePath(install_dir_, name);
-}
-
 bool GsiInstaller::CreateInstallStatusFile() {
-    if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) {
-        PLOG(ERROR) << "write " << kGsiInstallStatusFile;
-        return false;
-    }
-    return true;
-}
-
-std::unique_ptr<LpMetadata> GsiInstaller::CreateMetadata() {
-    auto writer = partitions_["system_gsi"].writer.get();
-
-    std::string data_device_path;
-    if (install_dir_ == kDefaultGsiImageFolder && !access(kUserdataDevice, F_OK)) {
-        auto actual_device = GetDevicePathForFile(writer);
-        if (actual_device != kUserdataDevice) {
-            LOG(ERROR) << "Image file did not resolve to userdata: " << actual_device;
-            return nullptr;
-        }
-        data_device_path = actual_device;
-    } else {
-        data_device_path = writer->bdev_path();
-    }
-    auto data_device_name = android::base::Basename(data_device_path);
-
-    PartitionOpener opener;
-    BlockDeviceInfo data_device_info;
-    if (!opener.GetInfo(data_device_path, &data_device_info)) {
-        LOG(ERROR) << "Error reading userdata partition";
-        return nullptr;
-    }
-
-    std::vector<BlockDeviceInfo> block_devices = {data_device_info};
-    auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1);
-    if (!builder) {
-        LOG(ERROR) << "Error creating metadata builder";
-        return nullptr;
-    }
-
-    for (const auto& [name, image] : partitions_) {
-        uint32_t flags = LP_PARTITION_ATTR_NONE;
-        if (name == "system_gsi") {
-            flags |= LP_PARTITION_ATTR_READONLY;
-        }
-        Partition* partition = builder->AddPartition(name, flags);
-        if (!partition) {
-            LOG(ERROR) << "Error adding " << name << " to partition table";
-            return nullptr;
-        }
-        if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) {
-            return nullptr;
-        }
-    }
-
-    auto metadata = builder->Export();
-    if (!metadata) {
-        LOG(ERROR) << "Error exporting partition table";
-        return nullptr;
-    }
-    return metadata;
-}
-
-bool GsiInstaller::CreateMetadataFile() {
-    if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) {
-        LOG(ERROR) << "Error writing GSI partition table image";
+    if (!android::base::WriteStringToFile("0", kDsuInstallStatusFile)) {
+        PLOG(ERROR) << "write " << kDsuInstallStatusFile;
         return false;
     }
     return true;
 }
 
 bool GsiInstaller::FormatUserdata() {
-    auto writer = OpenPartition("userdata_gsi");
-    if (!writer) {
+    auto device = OpenPartition("userdata_gsi");
+    if (!device) {
         return false;
     }
 
     // libcutils checks the first 4K, no matter the block size.
     std::string zeroes(4096, 0);
-    if (!writer->Write(zeroes.data(), zeroes.size())) {
+    if (!android::base::WriteFully(device->fd(), zeroes.data(), zeroes.size())) {
         PLOG(ERROR) << "write userdata_gsi";
         return false;
     }
     return true;
 }
 
-bool GsiInstaller::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition,
-                                      const Image& image, const std::string& block_device) {
-    uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE;
-    for (const auto& extent : image.writer->extents()) {
-        // :TODO: block size check for length, not sector size
-        if (extent.fe_length % LP_SECTOR_SIZE != 0) {
-            LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length;
-            return false;
-        }
-        if (extent.fe_physical % LP_SECTOR_SIZE != 0) {
-            LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical;
-            return false;
-        }
-
-        uint64_t num_sectors =
-                std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed);
-        if (!num_sectors || !sectors_needed) {
-            // This should never happen, but we include it just in case. It would
-            // indicate that the last filesystem block had multiple extents.
-            LOG(WARNING) << "FiemapWriter allocated extra blocks";
-            break;
-        }
-
-        uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
-        if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) {
-            LOG(ERROR) << "Could not add extent to lp metadata";
-            return false;
-        }
-
-        sectors_needed -= num_sectors;
-    }
-    return true;
-}
-
-static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) {
-    const LpMetadataPartition* partition = FindPartition(metadata, name);
-    if (!partition) {
-        return 0;
-    }
-    return android::fs_mgr::GetPartitionSize(metadata, *partition);
-}
-
-int GsiInstaller::GetExistingImage(const LpMetadata& metadata, const std::string& name,
-                                   Image* image) {
-    int error;
-    std::string path = GetImagePath(name);
-    auto writer = CreateFiemapWriter(path.c_str(), 0, &error);
-    if (!writer) {
-        return error;
-    }
-
-    // Even after recovering the FIEMAP, we also need to know the exact intended
-    // size of the image, since FiemapWriter may have extended the final block.
-    uint64_t actual_size = GetPartitionSize(metadata, name);
-    if (!actual_size) {
-        LOG(ERROR) << "Could not determine the pre-existing size of " << name;
-        return IGsiService::INSTALL_ERROR_GENERIC;
-    }
-    image->writer = std::move(writer);
-    image->actual_size = actual_size;
-    return IGsiService::INSTALL_OK;
-}
-
 int GsiInstaller::SetGsiBootable(bool one_shot) {
     if (gsi_bytes_written_ != gsi_size_) {
         // We cannot boot if the image is incomplete.
@@ -585,27 +321,27 @@
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
-    if (!system_writer_->Flush()) {
+    if (fsync(system_device_->fd())) {
+        PLOG(ERROR) << "fsync failed for system_gsi";
+        return IGsiService::INSTALL_ERROR_GENERIC;
+    }
+    system_device_ = {};
+
+    // If files moved (are no longer pinned), the metadata file will be invalid.
+    // This check can be removed once b/133967059 is fixed.
+    if (!images_->Validate()) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
-    // If files moved (are no longer pinned), the metadata file will be invalid.
-    for (const auto& [name, image] : partitions_) {
-        if (!image.writer->HasPinnedExtents()) {
-            LOG(ERROR) << name << " no longer has pinned extents";
-            return IGsiService::INSTALL_ERROR_GENERIC;
-        }
-    }
-
     // Remember the installation directory.
-    if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) {
-        PLOG(ERROR) << "write failed: " << kGsiInstallDirFile;
+    if (!android::base::WriteStringToFile(install_dir_, kDsuInstallDirFile)) {
+        PLOG(ERROR) << "write failed: " << kDsuInstallDirFile;
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
     // Note: create the install status file last, since this is the actual boot
     // indicator.
-    if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) {
+    if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
@@ -613,35 +349,12 @@
     return IGsiService::INSTALL_OK;
 }
 
-int GsiInstaller::RebuildInstallState() {
-    if (int error = DetermineReadWriteMethod()) {
-        return error;
-    }
-
-    // Note: this metadata is only used to recover the original partition sizes.
-    // We do not trust the extent information, which will get rebuilt later.
-    auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile);
-    if (!old_metadata) {
-        LOG(ERROR) << "GSI install is incomplete";
-        return IGsiService::INSTALL_ERROR_GENERIC;
-    }
-
-    // Recover parition information.
-    Image userdata_image;
-    if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) {
-        return error;
-    }
-    partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image)));
-
-    Image system_image;
-    if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) {
-        return error;
-    }
-    partitions_.emplace(std::make_pair("system_gsi", std::move(system_image)));
-
-    metadata_ = CreateMetadata();
-    if (!metadata_) {
-        return IGsiService::INSTALL_ERROR_GENERIC;
+int GsiInstaller::CheckInstallState() {
+    std::vector<std::string> gsi_images = {"system_gsi", "userdata_gsi"};
+    for (const auto& image : gsi_images) {
+        if (!images_->PartitionExists(image) || !images_->BackingImageExists(image)) {
+            return IGsiService::INSTALL_ERROR_GENERIC;
+        }
     }
     return IGsiService::INSTALL_OK;
 }
@@ -654,22 +367,22 @@
         return IGsiService::INSTALL_OK;
     }
 
-    if (int error = RebuildInstallState()) {
+    if (int error = CheckInstallState()) {
         return error;
     }
-    if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) {
+    if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
     return IGsiService::INSTALL_OK;
 }
 
 int GsiInstaller::WipeUserdata() {
-    if (int error = RebuildInstallState()) {
+    if (int error = CheckInstallState()) {
         return error;
     }
 
-    auto writer = OpenPartition("userdata_gsi");
-    if (!writer) {
+    auto device = OpenPartition("userdata_gsi");
+    if (!device) {
         return IGsiService::INSTALL_ERROR_GENERIC;
     }
 
@@ -678,9 +391,9 @@
     static constexpr uint64_t kEraseSize = 1024 * 1024;
 
     std::string zeroes(4096, 0);
-    uint64_t erase_size = std::min(kEraseSize, writer->Size());
+    uint64_t erase_size = std::min(kEraseSize, get_block_device_size(device->fd()));
     for (uint64_t i = 0; i < erase_size; i += zeroes.size()) {
-        if (!writer->Write(zeroes.data(), zeroes.size())) {
+        if (!android::base::WriteFully(device->fd(), zeroes.data(), zeroes.size())) {
             PLOG(ERROR) << "write userdata_gsi";
             return IGsiService::INSTALL_ERROR_GENERIC;
         }
diff --git a/gsi_installer.h b/gsi_installer.h
index ece659d..9f9a94f 100644
--- a/gsi_installer.h
+++ b/gsi_installer.h
@@ -21,7 +21,8 @@
 #include <string>
 
 #include <android/gsi/IGsiService.h>
-#include <libfiemap/split_fiemap_writer.h>
+#include <android/gsi/MappedImage.h>
+#include <libfiemap/image_manager.h>
 #include <liblp/builder.h>
 
 namespace android {
@@ -30,6 +31,9 @@
 class GsiService;
 
 class GsiInstaller final {
+    using ImageManager = android::fiemap::ImageManager;
+    using MappedDevice = android::fiemap::MappedDevice;
+
   public:
     // Constructor for a new GSI installation.
     GsiInstaller(GsiService* service, const GsiInstallParams& params);
@@ -49,78 +53,36 @@
 
     // Clean up install state if gsid crashed and restarted.
     static void PostInstallCleanup();
-
-    // This helper class will redirect writes to either a SplitFiemap or
-    // device-mapper.
-    class WriteHelper {
-      public:
-        virtual ~WriteHelper(){};
-        virtual bool Write(const void* data, uint64_t bytes) = 0;
-        virtual bool Flush() = 0;
-        virtual uint64_t Size() = 0;
-
-        WriteHelper() = default;
-        WriteHelper(const WriteHelper&) = delete;
-        WriteHelper& operator=(const WriteHelper&) = delete;
-        WriteHelper& operator=(WriteHelper&&) = delete;
-        WriteHelper(WriteHelper&&) = delete;
-    };
+    static void PostInstallCleanup(ImageManager* manager);
 
     const std::string& install_dir() const { return install_dir_; }
     uint64_t userdata_size() const { return userdata_size_; }
 
   private:
-    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
-    using LpMetadata = android::fs_mgr::LpMetadata;
-    using SplitFiemap = android::fiemap::SplitFiemap;
-
-    // The image file may be larger than the requested size, due to alignment,
-    // so we must track the requested size as well.
-    struct Image {
-        std::unique_ptr<SplitFiemap> writer;
-        uint64_t actual_size;
-    };
-
     int PerformSanityChecks();
     int PreallocateFiles();
     int PreallocateUserdata();
     int PreallocateSystem();
-    int DetermineReadWriteMethod();
     bool FormatUserdata();
-    bool AddPartitionFiemap(MetadataBuilder* builder, android::fs_mgr::Partition* partition,
-                            const Image& image, const std::string& block_device);
-    std::unique_ptr<LpMetadata> CreateMetadata();
-    std::unique_ptr<SplitFiemap> CreateFiemapWriter(const std::string& path, uint64_t size,
-                                                    int* error);
-    std::unique_ptr<WriteHelper> OpenPartition(const std::string& name);
-    int GetExistingImage(const LpMetadata& metadata, const std::string& name, Image* image);
-    int RebuildInstallState();
+    bool CreateImage(const std::string& name, uint64_t size, bool readonly);
+    std::unique_ptr<MappedDevice> OpenPartition(const std::string& name);
+    int CheckInstallState();
     bool CreateInstallStatusFile();
-    bool CreateMetadataFile();
     bool SetBootMode(bool one_shot);
-    std::string GetImagePath(const std::string& name);
 
     GsiService* service_;
 
     std::string install_dir_;
-    std::string userdata_gsi_path_;
-    std::string system_gsi_path_;
-    uint64_t userdata_block_size_ = 0;
-    uint64_t system_block_size_ = 0;
+    std::unique_ptr<ImageManager> images_;
     uint64_t gsi_size_ = 0;
     uint64_t userdata_size_ = 0;
-    bool can_use_devicemapper_ = false;
     bool wipe_userdata_ = false;
     bool wipe_userdata_on_failure_ = false;
     // Remaining data we're waiting to receive for the GSI image.
     uint64_t gsi_bytes_written_ = 0;
     bool succeeded_ = false;
 
-    std::unique_ptr<WriteHelper> system_writer_;
-
-    // This is used to track which GSI partitions have been created.
-    std::map<std::string, Image> partitions_;
-    std::unique_ptr<LpMetadata> metadata_;
+    std::unique_ptr<MappedDevice> system_device_;
 };
 
 }  // namespace gsi
diff --git a/gsi_service.cpp b/gsi_service.cpp
index 3d04fe1..4835d1f 100644
--- a/gsi_service.cpp
+++ b/gsi_service.cpp
@@ -32,8 +32,11 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android/gsi/BnImageService.h>
 #include <android/gsi/IGsiService.h>
+#include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
+#include <libfiemap/image_manager.h>
 #include <private/android_filesystem_config.h>
 
 #include "file_paths.h"
@@ -48,19 +51,44 @@
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
-void GsiService::Register() {
-    auto ret = android::BinderService<GsiService>::publish();
+android::wp<GsiService> GsiService::sInstance;
+
+void Gsid::Register() {
+    auto ret = android::BinderService<Gsid>::publish();
     if (ret != android::OK) {
         LOG(FATAL) << "Could not register gsi service: " << ret;
     }
 }
 
-GsiService::GsiService() {
+binder::Status Gsid::getClient(android::sp<IGsiService>* _aidl_return) {
+    *_aidl_return = GsiService::Get(this);
+    return binder::Status::ok();
+}
+
+GsiService::GsiService(Gsid* parent) : parent_(parent) {
     progress_ = {};
     GsiInstaller::PostInstallCleanup();
 }
 
-GsiService::~GsiService() {}
+GsiService::~GsiService() {
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    if (sInstance == this) {
+        // No more consumers, gracefully shut down gsid.
+        exit(0);
+    }
+}
+
+android::sp<IGsiService> GsiService::Get(Gsid* parent) {
+    std::lock_guard<std::mutex> guard(parent->lock());
+
+    android::sp<GsiService> service = sInstance.promote();
+    if (!service) {
+        service = new GsiService(parent);
+        sInstance = service.get();
+    }
+    return service.get();
+}
 
 #define ENFORCE_SYSTEM                      \
     do {                                    \
@@ -86,7 +114,7 @@
 binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params,
                                            int* _aidl_return) {
     ENFORCE_SYSTEM;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     // Make sure any interrupted installations are cleaned up.
     installer_ = nullptr;
@@ -111,7 +139,7 @@
 binder::Status GsiService::commitGsiChunkFromStream(const android::os::ParcelFileDescriptor& stream,
                                                     int64_t bytes, bool* _aidl_return) {
     ENFORCE_SYSTEM;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (!installer_) {
         *_aidl_return = false;
@@ -153,7 +181,7 @@
 binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& bytes,
                                                     bool* _aidl_return) {
     ENFORCE_SYSTEM;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (!installer_) {
         *_aidl_return = false;
@@ -165,7 +193,7 @@
 }
 
 binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) {
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (installer_) {
         ENFORCE_SYSTEM;
@@ -185,7 +213,7 @@
 
 binder::Status GsiService::isGsiEnabled(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
     std::string boot_key;
     if (!GetInstallStatus(&boot_key)) {
         *_aidl_return = false;
@@ -197,7 +225,7 @@
 
 binder::Status GsiService::removeGsiInstall(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     // Just in case an install was left hanging.
     std::string install_dir;
@@ -218,7 +246,7 @@
 
 binder::Status GsiService::disableGsiInstall(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     *_aidl_return = DisableGsiInstall();
     return binder::Status::ok();
@@ -226,7 +254,7 @@
 
 binder::Status GsiService::isGsiRunning(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     *_aidl_return = IsGsiRunning();
     return binder::Status::ok();
@@ -234,7 +262,7 @@
 
 binder::Status GsiService::isGsiInstalled(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     *_aidl_return = IsGsiInstalled();
     return binder::Status::ok();
@@ -242,7 +270,7 @@
 
 binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     *_aidl_return = !!installer_;
     return binder::Status::ok();
@@ -251,7 +279,7 @@
 binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) {
     ENFORCE_SYSTEM;
     should_abort_ = true;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     should_abort_ = false;
     installer_ = nullptr;
@@ -262,7 +290,7 @@
 
 binder::Status GsiService::getGsiBootStatus(int* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (!IsGsiInstalled()) {
         *_aidl_return = BOOT_STATUS_NOT_INSTALLED;
@@ -271,12 +299,12 @@
 
     std::string boot_key;
     if (!GetInstallStatus(&boot_key)) {
-        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
+        PLOG(ERROR) << "read " << kDsuInstallStatusFile;
         *_aidl_return = BOOT_STATUS_NOT_INSTALLED;
         return binder::Status::ok();
     }
 
-    bool single_boot = !access(kGsiOneShotBootFile, F_OK);
+    bool single_boot = !access(kDsuOneShotBootFile, F_OK);
 
     if (boot_key == kInstallStatusWipe) {
         // This overrides all other statuses.
@@ -301,7 +329,7 @@
 
 binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) {
     ENFORCE_SYSTEM;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     *_aidl_return = -1;
 
@@ -323,17 +351,10 @@
         }
         *_aidl_return = size;
     } else {
-        // Stat the size of the userdata file.
-        auto userdata_gsi = GetInstalledImagePath("userdata_gsi");
-        struct stat s;
-        if (stat(userdata_gsi.c_str(), &s)) {
-            if (errno != ENOENT) {
-                PLOG(ERROR) << "open " << userdata_gsi;
-                return binder::Status::ok();
+        if (auto manager = ImageManager::Open(kDsuMetadataDir, GetInstalledImageDir())) {
+            if (auto device = MappedDevice::Open(manager.get(), 10s, "userdata_gsi")) {
+                *_aidl_return = get_block_device_size(device->fd());
             }
-            *_aidl_return = 0;
-        } else {
-            *_aidl_return = s.st_size;
         }
     }
     return binder::Status::ok();
@@ -341,7 +362,7 @@
 
 binder::Status GsiService::getInstalledGsiImageDir(std::string* _aidl_return) {
     ENFORCE_SYSTEM;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (IsGsiInstalled()) {
         *_aidl_return = GetInstalledImageDir();
@@ -351,7 +372,7 @@
 
 binder::Status GsiService::wipeGsiUserdata(int* _aidl_return) {
     ENFORCE_SYSTEM_OR_SHELL;
-    std::lock_guard<std::mutex> guard(main_lock_);
+    std::lock_guard<std::mutex> guard(parent_->lock());
 
     if (IsGsiRunning() || !IsGsiInstalled()) {
         *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC;
@@ -364,6 +385,143 @@
     return binder::Status::ok();
 }
 
+static binder::Status BinderError(const std::string& message) {
+    return binder::Status::fromExceptionCode(binder::Status::EX_SERVICE_SPECIFIC,
+                                             String8(message.c_str()));
+}
+
+static binder::Status UidSecurityError() {
+    uid_t uid = IPCThreadState::self()->getCallingUid();
+    auto message = StringPrintf("UID %d is not allowed", uid);
+    return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(message.c_str()));
+}
+
+class ImageService : public BinderService<ImageService>, public BnImageService {
+  public:
+    ImageService(GsiService* service, std::unique_ptr<ImageManager>&& impl, uid_t uid);
+    binder::Status createBackingImage(const std::string& name, int64_t size, int flags) override;
+    binder::Status deleteBackingImage(const std::string& name) override;
+    binder::Status mapImageDevice(const std::string& name, int32_t timeout_ms,
+                                  MappedImage* mapping) override;
+    binder::Status unmapImageDevice(const std::string& name) override;
+    binder::Status backingImageExists(const std::string& name, bool* _aidl_return) override;
+    binder::Status isImageMapped(const std::string& name, bool* _aidl_return) override;
+
+  private:
+    bool CheckUid();
+
+    android::sp<GsiService> service_;
+    android::sp<Gsid> parent_;
+    std::unique_ptr<ImageManager> impl_;
+    uid_t uid_;
+};
+
+ImageService::ImageService(GsiService* service, std::unique_ptr<ImageManager>&& impl, uid_t uid)
+    : service_(service), parent_(service->parent()), impl_(std::move(impl)), uid_(uid) {}
+
+binder::Status ImageService::createBackingImage(const std::string& name, int64_t size, int flags) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    if (!impl_->CreateBackingImage(name, size, flags, nullptr)) {
+        return BinderError("Failed to create");
+    }
+    return binder::Status::ok();
+}
+
+binder::Status ImageService::deleteBackingImage(const std::string& name) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    if (!impl_->DeleteBackingImage(name)) {
+        return BinderError("Failed to delete");
+    }
+    return binder::Status::ok();
+}
+
+binder::Status ImageService::mapImageDevice(const std::string& name, int32_t timeout_ms,
+                                            MappedImage* mapping) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    if (!impl_->MapImageDevice(name, std::chrono::milliseconds(timeout_ms), &mapping->path)) {
+        return BinderError("Failed to map");
+    }
+    return binder::Status::ok();
+}
+
+binder::Status ImageService::unmapImageDevice(const std::string& name) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    if (!impl_->UnmapImageDevice(name)) {
+        return BinderError("Failed to unmap");
+    }
+    return binder::Status::ok();
+}
+
+binder::Status ImageService::backingImageExists(const std::string& name, bool* _aidl_return) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    *_aidl_return = impl_->BackingImageExists(name);
+    return binder::Status::ok();
+}
+
+binder::Status ImageService::isImageMapped(const std::string& name, bool* _aidl_return) {
+    if (!CheckUid()) return UidSecurityError();
+
+    std::lock_guard<std::mutex> guard(parent_->lock());
+
+    *_aidl_return = impl_->IsImageMapped(name);
+    return binder::Status::ok();
+}
+
+bool ImageService::CheckUid() {
+    return uid_ == IPCThreadState::self()->getCallingUid();
+}
+
+binder::Status GsiService::openImageService(const std::string& prefix,
+                                            android::sp<IImageService>* _aidl_return) {
+    static constexpr char kImageMetadataPrefix[] = "/metadata/gsi/";
+    static constexpr char kImageDataPrefix[] = "/data/gsi/";
+
+    auto in_metadata_dir = kImageMetadataPrefix + prefix;
+    auto in_data_dir = kImageDataPrefix + prefix;
+
+    std::string metadata_dir, data_dir;
+    if (!android::base::Realpath(in_metadata_dir, &metadata_dir)) {
+        PLOG(ERROR) << "realpath failed: " << metadata_dir;
+        return BinderError("Invalid path");
+    }
+    if (!android::base::Realpath(in_data_dir, &data_dir)) {
+        PLOG(ERROR) << "realpath failed: " << data_dir;
+        return BinderError("Invalid path");
+    }
+    if (!android::base::StartsWith(metadata_dir, kImageMetadataPrefix) ||
+        !android::base::StartsWith(data_dir, kImageDataPrefix)) {
+        return BinderError("Invalid path");
+    }
+
+    uid_t uid = IPCThreadState::self()->getCallingUid();
+    if (uid != AID_ROOT) {
+        return UidSecurityError();
+    }
+
+    auto impl = ImageManager::Open(metadata_dir, data_dir);
+    if (!impl) {
+        return BinderError("Unknown error");
+    }
+
+    *_aidl_return = new ImageService(this, std::move(impl), uid);
+    return binder::Status::ok();
+}
+
 binder::Status GsiService::CheckUid(AccessLevel level) {
     std::vector<uid_t> allowed_uids{AID_ROOT, AID_SYSTEM};
     if (level == AccessLevel::SystemOrShell) {
@@ -376,9 +534,7 @@
             return binder::Status::ok();
         }
     }
-
-    auto message = StringPrintf("UID %d is not allowed", uid);
-    return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(message.c_str()));
+    return UidSecurityError();
 }
 
 static bool IsExternalStoragePath(const std::string& path) {
@@ -404,7 +560,7 @@
     // specifying the top-level folder, and then we choose the correct location
     // underneath.
     if (params->installDir.empty() || params->installDir == "/data/gsi") {
-        params->installDir = kDefaultGsiImageFolder;
+        params->installDir = kDefaultDsuImageFolder;
     }
 
     // Normalize the path and add a trailing slash.
@@ -413,8 +569,7 @@
         PLOG(ERROR) << "realpath failed: " << origInstallDir;
         return INSTALL_ERROR_GENERIC;
     }
-    // Ensure the path ends in / for consistency. Even though GetImagePath()
-    // does this already, we want it to appear this way in install_dir.
+    // Ensure the path ends in / for consistency.
     if (!android::base::EndsWith(params->installDir, "/")) {
         params->installDir += "/";
     }
@@ -435,7 +590,7 @@
             LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once";
             return INSTALL_ERROR_GENERIC;
         }
-    } else if (params->installDir != kDefaultGsiImageFolder) {
+    } else if (params->installDir != kDefaultDsuImageFolder) {
         LOG(ERROR) << "cannot install GSI to " << params->installDir;
         return INSTALL_ERROR_GENERIC;
     }
@@ -452,26 +607,14 @@
     return INSTALL_OK;
 }
 
-std::string GsiService::GetImagePath(const std::string& image_dir, const std::string& name) {
-    std::string dir = image_dir;
-    if (!android::base::EndsWith(dir, "/")) {
-        dir += "/";
-    }
-    return dir + name + ".img";
-}
-
 std::string GsiService::GetInstalledImageDir() {
     // If there's no install left, just return /data/gsi since that's where
     // installs go by default.
     std::string dir;
-    if (android::base::ReadFileToString(kGsiInstallDirFile, &dir)) {
+    if (android::base::ReadFileToString(kDsuInstallDirFile, &dir)) {
         return dir;
     }
-    return kDefaultGsiImageFolder;
-}
-
-std::string GsiService::GetInstalledImagePath(const std::string& name) {
-    return GetImagePath(GetInstalledImageDir(), name);
+    return kDefaultDsuImageFolder;
 }
 
 int GsiService::ReenableGsi(bool one_shot) {
@@ -482,7 +625,7 @@
 
     std::string boot_key;
     if (!GetInstallStatus(&boot_key)) {
-        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
+        PLOG(ERROR) << "read " << kDsuInstallStatusFile;
         return INSTALL_ERROR_GENERIC;
     }
     if (boot_key != kInstallStatusDisabled) {
@@ -496,24 +639,20 @@
 
 bool GsiService::RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata) {
     bool ok = true;
-    std::string message;
-    if (!SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "system_gsi"), &message)) {
-        LOG(ERROR) << message;
-        ok = false;
-    }
-    if (wipeUserdata &&
-        !SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "userdata_gsi"), &message)) {
-        LOG(ERROR) << message;
-        ok = false;
+    if (auto manager = ImageManager::Open(kDsuMetadataDir, install_dir)) {
+        ok &= manager->DeleteBackingImage("system_gsi");
+        if (wipeUserdata) {
+            ok &= manager->DeleteBackingImage("userdata_gsi");
+        }
     }
 
     std::vector<std::string> files{
-            kGsiInstallStatusFile,
-            kGsiLpMetadataFile,
-            kGsiOneShotBootFile,
-            kGsiInstallDirFile,
+            kDsuInstallStatusFile,
+            kDsuOneShotBootFile,
+            kDsuInstallDirFile,
     };
     for (const auto& file : files) {
+        std::string message;
         if (!android::base::RemoveFileIfExists(file, &message)) {
             LOG(ERROR) << message;
             ok = false;
@@ -545,7 +684,7 @@
 
     std::string boot_key;
     if (!GetInstallStatus(&boot_key)) {
-        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
+        PLOG(ERROR) << "read " << kDsuInstallStatusFile;
         return;
     }
 
@@ -561,8 +700,8 @@
         int ignore;
         if (GetBootAttempts(boot_key, &ignore)) {
             // Mark the GSI as having successfully booted.
-            if (!android::base::WriteStringToFile(kInstallStatusOk, kGsiInstallStatusFile)) {
-                PLOG(ERROR) << "write " << kGsiInstallStatusFile;
+            if (!android::base::WriteStringToFile(kInstallStatusOk, kDsuInstallStatusFile)) {
+                PLOG(ERROR) << "write " << kDsuInstallStatusFile;
             }
         }
     }
diff --git a/gsi_service.h b/gsi_service.h
index 397d2f2..30ec2d7 100644
--- a/gsi_service.h
+++ b/gsi_service.h
@@ -24,6 +24,7 @@
 
 #include <android-base/unique_fd.h>
 #include <android/gsi/BnGsiService.h>
+#include <android/gsi/BnGsid.h>
 #include <binder/BinderService.h>
 #include <libfiemap/split_fiemap_writer.h>
 #include <liblp/builder.h>
@@ -34,13 +35,28 @@
 namespace android {
 namespace gsi {
 
-class GsiService : public BinderService<GsiService>, public BnGsiService {
+class Gsid : public BinderService<Gsid>, public BnGsid {
   public:
     static void Register();
+    static char const* getServiceName() { return kGsiServiceName; }
 
-    GsiService();
+    binder::Status getClient(android::sp<IGsiService>* _aidl_return) override;
+
+  private:
+    friend class GsiService;
+    friend class ImageService;
+
+    std::mutex& lock() { return lock_; }
+
+    std::mutex lock_;
+};
+
+class GsiService : public BinderService<GsiService>, public BnGsiService {
+  public:
     ~GsiService() override;
 
+    static android::sp<IGsiService> Get(Gsid* parent);
+
     binder::Status startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata,
                                    int* _aidl_return) override;
     binder::Status beginGsiInstall(const GsiInstallParams& params, int* _aidl_return) override;
@@ -61,31 +77,24 @@
     binder::Status getGsiBootStatus(int* _aidl_return) override;
     binder::Status getInstalledGsiImageDir(std::string* _aidl_return) override;
     binder::Status wipeGsiUserdata(int* _aidl_return) override;
+    binder::Status openImageService(const std::string& prefix,
+                                    android::sp<IImageService>* _aidl_return) override;
 
     // This is in GsiService, rather than GsiInstaller, since we need to access
     // it outside of the main lock which protects the unique_ptr.
     void StartAsyncOperation(const std::string& step, int64_t total_bytes);
     void UpdateProgress(int status, int64_t bytes_processed);
 
-    static char const* getServiceName() { return kGsiServiceName; }
-
     // Helper methods for GsiInstaller.
-    static std::string GetImagePath(const std::string& image_dir, const std::string& name);
     static bool RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata);
     bool should_abort() const { return should_abort_; }
+    Gsid* parent() const { return parent_.get(); }
 
     static void RunStartupTasks();
+    static std::string GetInstalledImageDir();
 
   private:
-    using LpMetadata = android::fs_mgr::LpMetadata;
-    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
-    using SplitFiemap = android::fiemap::SplitFiemap;
-
-    struct Image {
-        std::unique_ptr<SplitFiemap> writer;
-        uint64_t actual_size;
-    };
-
+    GsiService(Gsid* parent);
     int ValidateInstallParams(GsiInstallParams* params);
     bool DisableGsiInstall();
     int ReenableGsi(bool one_shot);
@@ -93,10 +102,9 @@
     enum class AccessLevel { System, SystemOrShell };
     binder::Status CheckUid(AccessLevel level = AccessLevel::System);
 
-    static std::string GetInstalledImagePath(const std::string& name);
-    static std::string GetInstalledImageDir();
+    static android::wp<GsiService> sInstance;
 
-    std::mutex main_lock_;
+    android::sp<Gsid> parent_;
     std::unique_ptr<GsiInstaller> installer_;
 
     // These are initialized or set in StartInstall().
diff --git a/gsi_tool.cpp b/gsi_tool.cpp
index f5f549f..c33e645 100644
--- a/gsi_tool.cpp
+++ b/gsi_tool.cpp
@@ -31,6 +31,7 @@
 #include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 #include <android/gsi/IGsiService.h>
+#include <android/gsi/IGsid.h>
 #include <binder/IServiceManager.h>
 #include <cutils/android_reboot.h>
 #include <libgsi/libgsi.h>
@@ -61,7 +62,7 @@
         // clang-format on
 };
 
-static sp<IGsiService> GetGsiService() {
+static sp<IGsid> GetGsiService() {
     if (android::base::GetProperty("init.svc.gsid", "") != "running") {
         if (!android::base::SetProperty("ctl.start", "gsid") ||
             !android::base::WaitForProperty("init.svc.gsid", "running", 5s)) {
@@ -77,7 +78,7 @@
         auto name = android::String16(kGsiServiceName);
         android::sp<android::IBinder> res = sm->checkService(name);
         if (res) {
-            return android::interface_cast<IGsiService>(res);
+            return android::interface_cast<IGsid>(res);
         }
         usleep(kSleepTimeMs * 1000);
     }
@@ -515,6 +516,13 @@
         return EX_NOPERM;
     }
 
+    android::sp<IGsiService> service;
+    auto status = gsid->getClient(&service);
+    if (!status.isOk()) {
+        std::cerr << "Could not get gsi client: " << ErrorMessage(status) << "\n";
+        return EX_SOFTWARE;
+    }
+
     if (1 >= argc) {
         std::cerr << "Expected command." << std::endl;
         return EX_USAGE;
@@ -528,6 +536,6 @@
         return usage(argc, argv);
     }
 
-    int rc = iter->second(gsid, argc - 1, argv + 1);
+    int rc = iter->second(service, argc - 1, argv + 1);
     return rc;
 }
diff --git a/gsid.rc b/gsid.rc
index dc62ede..2831df2 100644
--- a/gsid.rc
+++ b/gsid.rc
@@ -1,4 +1,5 @@
 service gsid /system/bin/gsid
+    oneshot
     disabled
     user root
     group root system media_rw
@@ -6,10 +7,12 @@
 on post-fs
     mkdir /metadata/gsi 0771 root system
     mkdir /metadata/gsi/dsu 0771 root system
+    mkdir /metadata/gsi/ota 0771 root system
 
 on post-fs-data
     mkdir /data/gsi 0700 root root
     mkdir /data/gsi/dsu 0700 root root
+    mkdir /data/gsi/ota 0700 root root
 
 on boot
     exec - root root -- /system/bin/gsid run-startup-tasks
diff --git a/libfiemap/Android.bp b/libfiemap/Android.bp
index b505628..9ae80e4 100644
--- a/libfiemap/Android.bp
+++ b/libfiemap/Android.bp
@@ -27,13 +27,18 @@
 
     srcs: [
         "fiemap_writer.cpp",
+        "image_manager.cpp",
+        "metadata.cpp",
         "split_fiemap_writer.cpp",
         "utility.cpp",
     ],
 
     static_libs: [
         "libdm",
+        "libext2_uuid",
         "libext4_utils",
+        "liblp",
+        "libfs_mgr",
     ],
 
     header_libs: [
@@ -42,6 +47,23 @@
     ],
 }
 
+cc_library_static {
+    name: "libfiemap_binder",
+    srcs: [
+        "binder.cpp",
+    ],
+    static_libs: [
+        "gsi_aidl_interface-cpp",
+        "libfiemap",
+        "libgsi",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+    ],
+    export_include_dirs: ["include"],
+}
+
 cc_test {
     name: "fiemap_writer_test",
     cflags: [
@@ -66,3 +88,24 @@
         "fiemap_writer_test.cpp",
     ],
 }
+
+cc_test {
+    name: "fiemap_image_test",
+    static_libs: [
+        "libdm",
+        "libext4_utils",
+        "libfiemap",
+        "libfs_mgr",
+        "liblp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+        "libcrypto_utils",
+        "libcutils",
+        "liblog",
+    ],
+    srcs: [
+        "image_test.cpp",
+    ],
+}
diff --git a/libfiemap/README.md b/libfiemap/README.md
new file mode 100644
index 0000000..62d610a
--- /dev/null
+++ b/libfiemap/README.md
@@ -0,0 +1,75 @@
+libfiemap
+=============
+
+`libfiemap` is a library for creating block-devices that are backed by
+storage in read-write partitions. It exists primary for gsid. Generally, the
+library works by using `libfiemap_writer` to allocate large files within
+filesystem, and then tracks their extents.
+
+There are three main uses for `libfiemap`:
+ - Creating images that will act as block devices. For example, gsid needs to
+   create a `system_gsi` image to store Dynamic System Updates.
+ - Mapping the image as a block device while /data is mounted. This is fairly
+   tricky and is described in more detail below.
+ - Mapping the image as a block device during first-stage init. This is simple
+   because it uses the same logic from dynamic partitions.
+
+Image creation is done through `SplitFiemap`. Depending on the file system,
+a large image may have to be split into multiple files. On Ext4 the limit is
+16GiB and on FAT32 it's 4GiB. Images are saved into `/data/gsi/<name>/`
+where `<name>` is chosen by the process requesting the image.
+
+At the same time, a file called `/metadata/gsi/<name>/lp_metadata` is created.
+This is a super partition header that allows first-stage init to create dynamic
+partitions from the image files. It also tracks the canonical size of the image,
+since the file size may be larger due to alignment.
+
+Mapping
+-------
+
+It is easy to make block devices out of blocks on `/data` when it is not
+mounted, so first-stage init has no issues mapping dynamic partitions from
+images. After `/data` is mounted however, there are two problems:
+ - `/data` is encrypted.
+ - `/dev/block/by-name/data` may be marked as in-use.
+
+We break the problem down into three scenarios.
+
+### FDE and Metadata Encrypted Devices
+
+When FDE or metadata encryption is used, `/data` is not mounted from
+`/dev/block/by-name/data`. Instead, it is mounted from an intermediate
+`dm-crypt` or `dm-default-key` device. This means the underlying device is
+not marked in use, and we can create new dm-linear devices on top of it.
+
+On these devices, a block device for an image will consist of a single
+device-mapper device with a `dm-linear` table entry for each extent in the
+backing file.
+
+### Unencrypted and FBE-encrypted Devices
+
+When a device is unencrypted, or is encrypted with FBE but not metadata
+encryption, we instead use a loop device with `LOOP_SET_DIRECT_IO` enabled.
+Since `/data/gsi` has encryption disabled, this means the raw blocks will be
+unencrypted as well.
+
+### Split Images
+
+If an image was too large to store a single file on the underlying filesystem,
+on an FBE/unencrypted device we will have multiple loop devices. In this case,
+we create a device-mapper device as well. For each loop device it will have one
+`dm-linear` table entry spanning the length of the device.
+
+State Tracking
+--------------
+
+It's important that we know whether or not an image is currently in-use by a
+block device. It could be catastrophic to write to a dm-linear device if the
+underlying blocks are no longer owned by the original file. Thus, when mapping
+an image, we create a property called `gsid.mapped_image.<name>` and set it to
+the path of the block device.
+
+Additionally, we create a `/metadata/gsi/<subdir>/<name>.status` file. Each
+line in this file denotes a dependency on either a device-mapper node or a loop
+device. When deleting a block device, this file is used to release all
+resources.
diff --git a/libfiemap/binder.cpp b/libfiemap/binder.cpp
new file mode 100644
index 0000000..86b59c7
--- /dev/null
+++ b/libfiemap/binder.cpp
@@ -0,0 +1,176 @@
+//
+// Copyright (C) 2019 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.
+//
+
+#if !defined(__ANDROID_RECOVERY__)
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/gsi/IGsiService.h>
+#include <android/gsi/IGsid.h>
+#include <binder/IServiceManager.h>
+#include <libfiemap/image_manager.h>
+#include <libgsi/libgsi.h>
+
+namespace android {
+namespace fiemap {
+
+using namespace android::gsi;
+using namespace std::chrono_literals;
+
+class ImageManagerBinder final : public IImageManager {
+  public:
+    ImageManagerBinder(android::sp<IGsiService>&& service, android::sp<IImageService>&& manager);
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+    bool DeleteBackingImage(const std::string& name) override;
+    bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                        std::string* path) override;
+    bool UnmapImageDevice(const std::string& name) override;
+    bool BackingImageExists(const std::string& name) override;
+    bool IsImageMapped(const std::string& name) override;
+
+  private:
+    android::sp<IGsiService> service_;
+    android::sp<IImageService> manager_;
+};
+
+ImageManagerBinder::ImageManagerBinder(android::sp<IGsiService>&& service,
+                                       android::sp<IImageService>&& manager)
+    : service_(std::move(service)), manager_(std::move(manager)) {}
+
+bool ImageManagerBinder::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+    auto status = manager_->createBackingImage(name, size, flags);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::DeleteBackingImage(const std::string& name) {
+    auto status = manager_->deleteBackingImage(name);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::MapImageDevice(const std::string& name,
+                                        const std::chrono::milliseconds& timeout_ms,
+                                        std::string* path) {
+    MappedImage map;
+    auto status = manager_->mapImageDevice(name, timeout_ms.count(), &map);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    *path = map.path;
+    return true;
+}
+
+bool ImageManagerBinder::UnmapImageDevice(const std::string& name) {
+    auto status = manager_->unmapImageDevice(name);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::BackingImageExists(const std::string& name) {
+    bool retval;
+    auto status = manager_->backingImageExists(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
+bool ImageManagerBinder::IsImageMapped(const std::string& name) {
+    bool retval;
+    auto status = manager_->isImageMapped(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
+static android::sp<IGsid> AcquireIGsid(const std::chrono::milliseconds& timeout_ms) {
+    if (android::base::GetProperty("init.svc.gsid", "") != "running") {
+        if (!android::base::SetProperty("ctl.start", "gsid") ||
+            !android::base::WaitForProperty("init.svc.gsid", "running", timeout_ms)) {
+            LOG(ERROR) << "Could not start the gsid service";
+            return nullptr;
+        }
+        // Sleep for 250ms to give the service time to register.
+        usleep(250 * 1000);
+    }
+    auto sm = android::defaultServiceManager();
+    auto name = android::String16(kGsiServiceName);
+    auto service = sm->checkService(name);
+    return android::interface_cast<IGsid>(service);
+}
+
+static android::sp<IGsid> GetGsiService(const std::chrono::milliseconds& timeout_ms) {
+    auto start_time = std::chrono::steady_clock::now();
+
+    std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero();
+    do {
+        if (auto gsid = AcquireIGsid(timeout_ms - elapsed); gsid != nullptr) {
+            return gsid;
+        }
+        auto now = std::chrono::steady_clock::now();
+        elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+    } while (elapsed <= timeout_ms);
+
+    LOG(ERROR) << "Timed out trying to acquire IGsid interface";
+    return nullptr;
+}
+
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+                                                   const std::chrono::milliseconds& timeout_ms) {
+    auto gsid = GetGsiService(timeout_ms);
+    if (!gsid) {
+        return nullptr;
+    }
+
+    android::sp<IGsiService> service;
+    auto status = gsid->getClient(&service);
+    if (!status.isOk() || !service) {
+        LOG(ERROR) << "Could not acquire IGsiService";
+        return nullptr;
+    }
+
+    android::sp<IImageService> manager;
+    status = service->openImageService(dir, &manager);
+    if (!status.isOk() || !manager) {
+        LOG(ERROR) << "Could not acquire IImageManager: " << status.exceptionMessage().string();
+        return nullptr;
+    }
+    return std::make_unique<ImageManagerBinder>(std::move(service), std::move(manager));
+}
+
+}  // namespace fiemap
+}  // namespace android
+
+#endif  // __ANDROID_RECOVERY__
diff --git a/libfiemap/fiemap_writer.cpp b/libfiemap/fiemap_writer.cpp
index 7a8c6f3..46fcb0e 100644
--- a/libfiemap/fiemap_writer.cpp
+++ b/libfiemap/fiemap_writer.cpp
@@ -285,7 +285,7 @@
     }
 
     uint64_t available_bytes = sfs.f_bsize * sfs.f_bavail;
-    if (available_bytes <= file_size) {
+    if (access(file_path.c_str(), F_OK) != 0 && available_bytes <= file_size) {
         LOG(ERROR) << "Not enough free space in file system to create file of size : " << file_size;
         return false;
     }
diff --git a/libfiemap/image_manager.cpp b/libfiemap/image_manager.cpp
new file mode 100644
index 0000000..e389905
--- /dev/null
+++ b/libfiemap/image_manager.cpp
@@ -0,0 +1,597 @@
+//
+// Copyright (C) 2019 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 <libfiemap/image_manager.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_mgr/file_wait.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/loop_control.h>
+#include <libfiemap/split_fiemap_writer.h>
+
+#include "metadata.h"
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace std::literals;
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::LoopControl;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetPartitionName;
+
+static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
+
+std::unique_ptr<IImageManager> __attribute__((weak))
+IImageManager::Open(const std::string& dir_prefix, const std::chrono::milliseconds& timeout_ms) {
+    (void)timeout_ms;
+    return ImageManager::Open(dir_prefix);
+}
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+    auto metadata_dir = "/metadata/gsi/" + dir_prefix;
+    auto data_dir = "/data/gsi/" + dir_prefix;
+    return Open(metadata_dir, data_dir);
+}
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
+                                                 const std::string& data_dir) {
+    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+}
+
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
+    : metadata_dir_(metadata_dir), data_dir_(data_dir) {}
+
+std::string ImageManager::GetImageHeaderPath(const std::string& name) {
+    return JoinPaths(data_dir_, name) + ".img";
+}
+
+// The status file has one entry per line, with each entry formatted as one of:
+//   dm:<name>
+//   loop:<path>
+//
+// This simplifies the process of tearing down a mapping, since we can simply
+// unmap each entry in the order it appears.
+std::string ImageManager::GetStatusFilePath(const std::string& image_name) {
+    return JoinPaths(metadata_dir_, image_name) + ".status";
+}
+
+static std::string GetStatusPropertyName(const std::string& image_name) {
+    // Note: we don't prefix |image_name|, because CreateLogicalPartition won't
+    // prefix the name either. There are no plans to change this at the moment,
+    // consumers of the image API must take care to use globally-unique image
+    // names.
+    return "gsid.mapped_image." + image_name;
+}
+
+bool ImageManager::IsImageMapped(const std::string& image_name) {
+    auto prop_name = GetStatusPropertyName(image_name);
+    if (android::base::GetProperty(prop_name, "").empty()) {
+        // If mapped in first-stage init, the dm-device will exist but not the
+        // property.
+        auto& dm = DeviceMapper::Instance();
+        return dm.GetState(image_name) != DmDeviceState::INVALID;
+    }
+    return true;
+}
+
+bool ImageManager::PartitionExists(const std::string& name) {
+    if (!MetadataExists(metadata_dir_)) {
+        return false;
+    }
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+    return !!FindPartition(*metadata.get(), name);
+}
+
+bool ImageManager::BackingImageExists(const std::string& name) {
+    auto header_file = GetImageHeaderPath(name);
+    return access(header_file.c_str(), F_OK) == 0;
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+    return CreateBackingImage(name, size, flags, nullptr);
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags,
+                                      std::function<bool(uint64_t, uint64_t)>&& on_progress) {
+    auto data_path = GetImageHeaderPath(name);
+    auto fw = SplitFiemap::Create(data_path, size, 0, on_progress);
+    if (!fw) {
+        return false;
+    }
+
+    // Except for testing, we do not allow persisting metadata that references
+    // device-mapper devices. It just doesn't make sense, because the device
+    // numbering may change on reboot. We allow it for testing since the images
+    // are not meant to survive reboot. Outside of tests, this can only happen
+    // if device-mapper is stacked in some complex way not supported by
+    // FiemapWriter.
+    auto device_path = GetDevicePathForFile(fw.get());
+    if (android::base::StartsWith(device_path, "/dev/block/dm-") &&
+        !android::base::StartsWith(metadata_dir_, kTestImageMetadataDir)) {
+        LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path;
+
+        fw = {};
+        SplitFiemap::RemoveSplitFiles(data_path);
+        return false;
+    }
+
+    bool readonly = !!(flags & CREATE_IMAGE_READONLY);
+    if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) {
+        return false;
+    }
+
+    if (flags & CREATE_IMAGE_ZERO_FILL) {
+        if (!ZeroFillNewImage(name)) {
+            DeleteBackingImage(name);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ImageManager::ZeroFillNewImage(const std::string& name) {
+    auto data_path = GetImageHeaderPath(name);
+
+    // See the comment in MapImageDevice() about how this works.
+    std::string block_device;
+    bool can_use_devicemapper;
+    if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) {
+        LOG(ERROR) << "Could not determine block device for " << data_path;
+        return false;
+    }
+
+    if (!can_use_devicemapper) {
+        // We've backed with loop devices, and since we store files in an
+        // unencrypted folder, the initial zeroes we wrote will suffice.
+        return true;
+    }
+
+    // data is dm-crypt, or FBE + dm-default-key. This means the zeroes written
+    // by libfiemap were encrypted, so we need to map the image in and correct
+    // this.
+    auto device = MappedDevice::Open(this, 10s, name);
+    if (!device) {
+        return false;
+    }
+
+    static constexpr size_t kChunkSize = 4096;
+    std::string zeroes(kChunkSize, '\0');
+
+    uint64_t remaining = get_block_device_size(device->fd());
+    if (!remaining) {
+        PLOG(ERROR) << "Could not get block device size for " << device->path();
+        return false;
+    }
+    while (remaining) {
+        uint64_t to_write = std::min(static_cast<uint64_t>(zeroes.size()), remaining);
+        if (!android::base::WriteFully(device->fd(), zeroes.data(),
+                                       static_cast<size_t>(to_write))) {
+            PLOG(ERROR) << "write failed: " << device->path();
+            return false;
+        }
+        remaining -= to_write;
+    }
+    return true;
+}
+
+bool ImageManager::DeleteBackingImage(const std::string& name) {
+    // For dm-linear devices sitting on top of /data, we cannot risk deleting
+    // the file. The underlying blocks could be reallocated by the filesystem.
+    if (IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is currently mapped to a block device";
+        return false;
+    }
+
+    std::string message;
+    auto header_file = GetImageHeaderPath(name);
+    if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
+        // This is fatal, because we don't want to leave these files dangling.
+        LOG(ERROR) << "Error removing image " << name << ": " << message;
+        return false;
+    }
+
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::RemoveFileIfExists(status_file)) {
+        LOG(ERROR) << "Error removing " << status_file << ": " << message;
+    }
+    return RemoveImageMetadata(metadata_dir_, name);
+}
+
+// Create a block device for an image file, using its extents in its
+// lp_metadata.
+bool ImageManager::MapWithDmLinear(const std::string& name, const std::string& block_device,
+                                   const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    // :TODO: refresh extents in metadata file until f2fs is fixed.
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+    if (!CreateLogicalPartition(block_device, *metadata.get(), name, true, timeout_ms, path)) {
+        LOG(ERROR) << "Error creating device-mapper node for image " << name;
+        return false;
+    }
+
+    auto status_string = "dm:" + name;
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_string, status_file)) {
+        PLOG(ERROR) << "Could not write status file: " << status_file;
+        DestroyLogicalPartition(name);
+        return false;
+    }
+    return true;
+}
+
+// Helper to create a loop device for a file.
+static bool CreateLoopDevice(LoopControl& control, const std::string& file,
+                             const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+    android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
+    if (file_fd < 0) {
+        PLOG(ERROR) << "Could not open file: " << file;
+        return false;
+    }
+    if (!control.Attach(file_fd, timeout_ms, path)) {
+        LOG(ERROR) << "Could not create loop device for: " << file;
+        return false;
+    }
+    LOG(INFO) << "Created loop device " << *path << " for file " << file;
+    return true;
+}
+
+class AutoDetachLoopDevices final {
+  public:
+    AutoDetachLoopDevices(LoopControl& control, const std::vector<std::string>& devices)
+        : control_(control), devices_(devices), commit_(false) {}
+
+    ~AutoDetachLoopDevices() {
+        if (commit_) return;
+        for (const auto& device : devices_) {
+            control_.Detach(device);
+        }
+    }
+
+    void Commit() { commit_ = true; }
+
+  private:
+    LoopControl& control_;
+    const std::vector<std::string>& devices_;
+    bool commit_;
+};
+
+// If an image is stored across multiple files, this takes a list of loop
+// devices and joins them together using device-mapper.
+bool ImageManager::MapWithLoopDeviceList(const std::vector<std::string>& device_list,
+                                         const std::string& name,
+                                         const std::chrono::milliseconds& timeout_ms,
+                                         std::string* path) {
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+    auto partition = FindPartition(*metadata.get(), name);
+    if (!partition) {
+        LOG(ERROR) << "Could not find image in metadata: " << name;
+        return false;
+    }
+
+    // Since extent lengths are in sector units, the size should be a multiple
+    // of the sector size.
+    uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition);
+    if (partition_size % LP_SECTOR_SIZE != 0) {
+        LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size
+                   << " bytes";
+        return false;
+    }
+
+    DmTable table;
+
+    uint64_t start_sector = 0;
+    uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+    for (const auto& block_device : device_list) {
+        // The final block device must be == partition_size, otherwise we
+        // can't find the AVB footer on verified partitions.
+        static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+        unique_fd fd(open(block_device.c_str(), kOpenFlags));
+        if (fd < 0) {
+            PLOG(ERROR) << "Open failed: " << block_device;
+            return false;
+        }
+
+        uint64_t file_size = get_block_device_size(fd);
+        uint64_t file_sectors = file_size / LP_SECTOR_SIZE;
+        uint64_t segment_size = std::min(file_sectors, sectors_needed);
+
+        table.Emplace<DmTargetLinear>(start_sector, segment_size, block_device, 0);
+
+        start_sector += segment_size;
+        sectors_needed -= segment_size;
+        if (sectors_needed == 0) {
+            break;
+        }
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+        LOG(ERROR) << "Could not create device-mapper device over loop set";
+        return false;
+    }
+
+    // Build the status file.
+    std::vector<std::string> lines;
+    lines.emplace_back("dm:" + name);
+    for (const auto& block_device : device_list) {
+        lines.emplace_back("loop:" + block_device);
+    }
+    auto status_message = android::base::Join(lines, "\n");
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_message, status_file)) {
+        PLOG(ERROR) << "Write failed: " << status_file;
+        dm.DeleteDevice(name);
+        return false;
+    }
+    return true;
+}
+
+static bool OptimizeLoopDevices(const std::vector<std::string>& device_list) {
+    for (const auto& device : device_list) {
+        unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW));
+        if (fd < 0) {
+            PLOG(ERROR) << "Open failed: " << device;
+            return false;
+        }
+        if (!LoopControl::EnableDirectIo(fd)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Helper to use one or more loop devices around image files.
+bool ImageManager::MapWithLoopDevice(const std::string& name,
+                                     const std::chrono::milliseconds& timeout_ms,
+                                     std::string* path) {
+    auto image_header = GetImageHeaderPath(name);
+
+    std::vector<std::string> file_list;
+    if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) {
+        LOG(ERROR) << "Could not get image file list";
+        return false;
+    }
+
+    // Map each image file as a loopback device.
+    LoopControl control;
+    std::vector<std::string> loop_devices;
+    AutoDetachLoopDevices auto_detach(control, loop_devices);
+
+    auto start_time = std::chrono::steady_clock::now();
+    for (const auto& file : file_list) {
+        auto now = std::chrono::steady_clock::now();
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+
+        std::string loop_device;
+        if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) {
+            break;
+        }
+        loop_devices.emplace_back(loop_device);
+    }
+    if (loop_devices.size() != file_list.size()) {
+        // The number of devices will mismatch if CreateLoopDevice() failed.
+        return false;
+    }
+
+    // If OptimizeLoopDevices fails, we'd use double the memory.
+    if (!OptimizeLoopDevices(loop_devices)) {
+        return false;
+    }
+
+    // If there's only one loop device (by far the most common case, splits
+    // will normally only happen on sdcards with FAT32), then just return that
+    // as the block device. Otherwise, we need to use dm-linear to stitch
+    // together all the loop devices we just created.
+    if (loop_devices.size() > 1) {
+        if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) {
+            return false;
+        }
+    }
+
+    auto status_message = "loop:" + loop_devices.back();
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_message, status_file)) {
+        PLOG(ERROR) << "Write failed: " << status_file;
+        return false;
+    }
+
+    auto_detach.Commit();
+
+    *path = loop_devices.back();
+    return true;
+}
+
+bool ImageManager::MapImageDevice(const std::string& name,
+                                  const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    if (IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is already mapped";
+        return false;
+    }
+
+    auto image_header = GetImageHeaderPath(name);
+
+    // If there is a device-mapper node wrapping the block device, then we're
+    // able to create another node around it; the dm layer does not carry the
+    // exclusion lock down the stack when a mount occurs.
+    //
+    // If there is no intermediate device-mapper node, then partitions cannot be
+    // opened writable due to sepolicy and exclusivity of having a mounted
+    // filesystem. This should only happen on devices with no encryption, or
+    // devices with FBE and no metadata encryption. For these cases it suffices
+    // to perform normal file writes to /data/gsi (which is unencrypted).
+    std::string block_device;
+    bool can_use_devicemapper;
+    if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
+        LOG(ERROR) << "Could not determine block device for " << image_header;
+        return false;
+    }
+
+    if (can_use_devicemapper) {
+        if (!android::base::StartsWith(data_dir_, "/data/gsi/")) {
+            LOG(ERROR) << "unexpected device-mapper node used to mount external media";
+            return false;
+        }
+        if (!MapWithDmLinear(name, block_device, timeout_ms, path)) {
+            return false;
+        }
+    } else if (!MapWithLoopDevice(name, timeout_ms, path)) {
+        return false;
+    }
+
+    // Set a property so we remember this is mapped.
+    auto prop_name = GetStatusPropertyName(name);
+    if (!android::base::SetProperty(prop_name, *path)) {
+        UnmapImageDevice(name, true);
+        return false;
+    }
+    return true;
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name) {
+    return UnmapImageDevice(name, false);
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name, bool force) {
+    if (!force && !IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is not mapped";
+        return false;
+    }
+    auto& dm = DeviceMapper::Instance();
+    LoopControl loop;
+
+    std::string status;
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::ReadFileToString(status_file, &status)) {
+        PLOG(ERROR) << "Read failed: " << status_file;
+        return false;
+    }
+
+    auto lines = android::base::Split(status, "\n");
+    for (const auto& line : lines) {
+        auto pieces = android::base::Split(line, ":");
+        if (pieces.size() != 2) {
+            LOG(ERROR) << "Unknown status line";
+            continue;
+        }
+        if (pieces[0] == "dm") {
+            // Failure to remove a dm node is fatal, since we can't safely
+            // remove the file or loop devices.
+            if (!dm.DeleteDevice(pieces[1]) && errno != ENOENT) {
+                return false;
+            }
+        } else if (pieces[0] == "loop") {
+            // Failure to remove a loop device is not fatal, since we can still
+            // remove the backing file if we want.
+            loop.Detach(pieces[1]);
+        } else {
+            LOG(ERROR) << "Unknown status: " << pieces[0];
+        }
+    }
+
+    std::string message;
+    if (!android::base::RemoveFileIfExists(status_file, &message)) {
+        LOG(ERROR) << "Could not remove " << status_file << ": " << message;
+    }
+
+    auto status_prop = GetStatusPropertyName(name);
+    android::base::SetProperty(status_prop, "");
+    return true;
+}
+
+bool ImageManager::RemoveAllImages() {
+    if (!MetadataExists(metadata_dir_)) {
+        return true;
+    }
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return RemoveAllMetadata(metadata_dir_);
+    }
+
+    bool ok = true;
+    for (const auto& partition : metadata->partitions) {
+        auto partition_name = GetPartitionName(partition);
+        ok &= DeleteBackingImage(partition_name);
+    }
+    return ok && RemoveAllMetadata(metadata_dir_);
+}
+
+bool ImageManager::Validate() {
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+
+    for (const auto& partition : metadata->partitions) {
+        auto name = GetPartitionName(partition);
+        auto image_path = GetImageHeaderPath(name);
+        auto fiemap = SplitFiemap::Open(image_path);
+        if (!fiemap || !fiemap->HasPinnedExtents()) {
+            LOG(ERROR) << "Image is missing or was moved: " << image_path;
+            return false;
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
+                                                 const std::chrono::milliseconds& timeout_ms,
+                                                 const std::string& name) {
+    std::string path;
+    if (!manager->MapImageDevice(name, timeout_ms, &path)) {
+        return nullptr;
+    }
+
+    auto device = std::unique_ptr<MappedDevice>(new MappedDevice(manager, name, path));
+    if (device->fd() < 0) {
+        return nullptr;
+    }
+    return device;
+}
+
+MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path)
+    : manager_(manager), name_(name), path_(path) {
+    // The device is already mapped; try and open it.
+    fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
+}
+
+MappedDevice::~MappedDevice() {
+    fd_ = {};
+    manager_->UnmapImageDevice(name_);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/libfiemap/image_test.cpp b/libfiemap/image_test.cpp
new file mode 100644
index 0000000..bbb6ac4
--- /dev/null
+++ b/libfiemap/image_test.cpp
@@ -0,0 +1,215 @@
+//
+// Copyright (C) 2019 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 <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_mgr/file_wait.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+using namespace android::dm;
+using namespace std::literals;
+using android::base::unique_fd;
+using android::fiemap::ImageManager;
+using android::fs_mgr::WaitForFile;
+
+static constexpr char kDataPath[] = "/data/gsi/test";
+static constexpr char kDataMountPath[] = "/data/gsi/test/mnt";
+static constexpr char kMetadataPath[] = "/metadata/gsi/test";
+
+static constexpr uint64_t kTestImageSize = 1024 * 1024;
+
+// This fixture is for tests against the device's native configuration.
+class NativeTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        manager_ = ImageManager::Open(kMetadataPath, kDataPath);
+        ASSERT_NE(manager_, nullptr);
+
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        base_name_ = tinfo->name();
+    }
+
+    void TearDown() override {
+        manager_->UnmapImageDevice(base_name_);
+        manager_->DeleteBackingImage(base_name_);
+    }
+
+    std::string PropertyName() { return "gsid.mapped_image." + base_name_; }
+
+    std::unique_ptr<ImageManager> manager_;
+    std::string base_name_;
+};
+
+TEST_F(NativeTest, CreateAndMap) {
+    ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path));
+    ASSERT_TRUE(manager_->IsImageMapped(base_name_));
+    ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), path);
+
+    {
+        unique_fd fd(open(path.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC));
+        ASSERT_GE(fd, 0);
+        ASSERT_EQ(get_block_device_size(fd), kTestImageSize);
+    }
+
+    ASSERT_TRUE(manager_->UnmapImageDevice(base_name_));
+    ASSERT_FALSE(manager_->IsImageMapped(base_name_));
+    ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), "");
+}
+
+// This fixture is for tests against a simulated device environment. Rather
+// than use /data, we create an image and then layer a new filesystem within
+// it. Each test then decides how to mount and create layered images. This
+// allows us to test FBE vs FDE configurations.
+class ImageTest : public ::testing::Test {
+  public:
+    ImageTest() : dm_(DeviceMapper::Instance()) {}
+
+    void SetUp() override {
+        manager_ = ImageManager::Open(kMetadataPath, kDataPath);
+        ASSERT_NE(manager_, nullptr);
+
+        submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, kDataPath + "/mnt"s);
+        ASSERT_NE(submanager_, nullptr);
+
+        // Ensure that metadata is cleared in between runs.
+        submanager_->RemoveAllImages();
+        manager_->RemoveAllImages();
+
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        base_name_ = tinfo->name();
+        test_image_name_ = base_name_ + "-base";
+        wrapper_device_name_ = base_name_ + "-wrapper";
+
+        ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr));
+        ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_));
+    }
+
+    void TearDown() override {
+        submanager_->UnmapImageDevice(test_image_name_);
+        umount(kDataMountPath);
+        dm_.DeleteDevice(wrapper_device_name_);
+        manager_->UnmapImageDevice(base_name_);
+        manager_->DeleteBackingImage(base_name_);
+    }
+
+  protected:
+    bool DoFormat(const std::string& device) {
+        // clang-format off
+        std::vector<std::string> mkfs_args = {
+            "/system/bin/mke2fs",
+            "-F",
+            "-b 4096",
+            "-t ext4",
+            "-m 0",
+            "-O has_journal",
+            device,
+            ">/dev/null",
+            "2>/dev/null",
+            "</dev/null",
+        };
+        // clang-format on
+        auto command = android::base::Join(mkfs_args, " ");
+        return system(command.c_str()) == 0;
+    }
+
+    std::unique_ptr<ImageManager> manager_;
+    std::unique_ptr<ImageManager> submanager_;
+
+    DeviceMapper& dm_;
+    std::string base_name_;
+    std::string base_device_;
+    std::string test_image_name_;
+    std::string wrapper_device_name_;
+};
+
+TEST_F(ImageTest, DirectMount) {
+    ASSERT_TRUE(DoFormat(base_device_));
+    ASSERT_EQ(mount(base_device_.c_str(), kDataMountPath, "ext4", 0, nullptr), 0);
+    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop"));
+}
+
+TEST_F(ImageTest, IndirectMount) {
+    // Create a simple wrapper around the base device that we'll mount from
+    // instead. This will simulate the code paths for dm-crypt/default-key/bow
+    // and force us to use device-mapper rather than loop devices.
+    uint64_t device_size = 0;
+    {
+        unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC));
+        ASSERT_GE(fd, 0);
+        device_size = get_block_device_size(fd);
+        ASSERT_EQ(device_size, kTestImageSize * 16);
+    }
+    uint64_t num_sectors = device_size / 512;
+
+    auto& dm = DeviceMapper::Instance();
+
+    DmTable table;
+    table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0);
+    ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table));
+
+    // Format and mount.
+    std::string wrapper_device;
+    ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device));
+    ASSERT_TRUE(WaitForFile(wrapper_device, 5s));
+    ASSERT_TRUE(DoFormat(wrapper_device));
+    ASSERT_EQ(mount(wrapper_device.c_str(), kDataMountPath, "ext4", 0, nullptr), 0);
+
+    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-"));
+}
+
+bool Mkdir(const std::string& path) {
+    if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+        std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    if (!Mkdir(kDataPath) || !Mkdir(kMetadataPath) || !Mkdir(kDataMountPath) ||
+        !Mkdir(kMetadataPath + "/mnt"s)) {
+        return 1;
+    }
+    return RUN_ALL_TESTS();
+}
diff --git a/libfiemap/include/libfiemap/image_manager.h b/libfiemap/include/libfiemap/image_manager.h
new file mode 100644
index 0000000..0d4df3c
--- /dev/null
+++ b/libfiemap/include/libfiemap/image_manager.h
@@ -0,0 +1,158 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <stdint.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace fiemap {
+
+class IImageManager {
+  public:
+    virtual ~IImageManager() {}
+
+    // When linking to libfiemap_binder, the Open() call will use binder.
+    // Otherwise, the Open() call will use the ImageManager implementation
+    // below.
+    static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
+                                               const std::chrono::milliseconds& timeout_ms);
+
+    // Flags for CreateBackingImage().
+    static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
+    static constexpr int CREATE_IMAGE_READONLY = 0x1;
+    static constexpr int CREATE_IMAGE_ZERO_FILL = 0x2;
+
+    // Create an image that can be mapped as a block-device. If |force_zero_fill|
+    // is true, the image will be zero-filled. Otherwise, the initial content
+    // of the image is undefined. If zero-fill is requested, and the operation
+    // cannot be completed, the image will be deleted and this function will
+    // return false.
+    virtual bool CreateBackingImage(const std::string& name, uint64_t size, int flags) = 0;
+
+    // Delete an image created with CreateBackingImage. Its entry will be
+    // removed from the associated lp_metadata file.
+    virtual bool DeleteBackingImage(const std::string& name) = 0;
+
+    // Create a block device for an image previously created with
+    // CreateBackingImage. This will wait for at most |timeout_ms| milliseconds
+    // for |path| to be available, and will return false if not available in
+    // the requested time. If |timeout_ms| is zero, this is NOT guaranteed to
+    // return true. A timeout of 10s is recommended.
+    //
+    // Note that snapshots created with a readonly flag are always mapped
+    // writable. The flag is persisted in the lp_metadata file however, so if
+    // fs_mgr::CreateLogicalPartition(s) is used, the flag will be respected.
+    virtual bool MapImageDevice(const std::string& name,
+                                const std::chrono::milliseconds& timeout_ms, std::string* path) = 0;
+
+    // Unmap a block device previously mapped with mapBackingImage.
+    virtual bool UnmapImageDevice(const std::string& name) = 0;
+
+    // Returns true whether the named backing image exists.
+    virtual bool BackingImageExists(const std::string& name) = 0;
+
+    // Returns true if the specified image is mapped to a device.
+    virtual bool IsImageMapped(const std::string& name) = 0;
+};
+
+class ImageManager final : public IImageManager {
+  public:
+    // Return an ImageManager for the given metadata and data directories. Both
+    // directories must already exist.
+    static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
+                                              const std::string& data_dir);
+
+    // Helper function that derives the metadata and data dirs given a single
+    // prefix.
+    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+
+    // Methods that must be implemented from IImageManager.
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+    bool DeleteBackingImage(const std::string& name) override;
+    bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                        std::string* path) override;
+    bool UnmapImageDevice(const std::string& name) override;
+    bool BackingImageExists(const std::string& name) override;
+    bool IsImageMapped(const std::string& name) override;
+
+    // Same as CreateBackingImage, but provides a progress notification.
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags,
+                            std::function<bool(uint64_t, uint64_t)>&& on_progress);
+
+    // Find and remove all images and metadata for a given image dir.
+    bool RemoveAllImages();
+
+    // Returns true if the named partition exists. This does not check the
+    // consistency of the backing image/data file.
+    bool PartitionExists(const std::string& name);
+
+    // Validates that all images still have pinned extents. This will be removed
+    // once b/134588268 is fixed.
+    bool Validate();
+
+  private:
+    ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+    std::string GetImageHeaderPath(const std::string& name);
+    std::string GetStatusFilePath(const std::string& image_name);
+    bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                           std::string* path);
+    bool MapWithLoopDeviceList(const std::vector<std::string>& device_list, const std::string& name,
+                               const std::chrono::milliseconds& timeout_ms, std::string* path);
+    bool MapWithDmLinear(const std::string& name, const std::string& block_device,
+                         const std::chrono::milliseconds& timeout_ms, std::string* path);
+    bool UnmapImageDevice(const std::string& name, bool force);
+    bool ZeroFillNewImage(const std::string& name);
+
+    ImageManager(const ImageManager&) = delete;
+    ImageManager& operator=(const ImageManager&) = delete;
+    ImageManager& operator=(ImageManager&&) = delete;
+    ImageManager(ImageManager&&) = delete;
+
+    std::string metadata_dir_;
+    std::string data_dir_;
+};
+
+// RAII helper class for mapping and opening devices with an ImageManager.
+class MappedDevice final {
+  public:
+    static std::unique_ptr<MappedDevice> Open(IImageManager* manager,
+                                              const std::chrono::milliseconds& timeout_ms,
+                                              const std::string& name);
+
+    ~MappedDevice();
+
+    int fd() const { return fd_; }
+    const std::string& path() const { return path_; }
+
+  protected:
+    MappedDevice(IImageManager* manager, const std::string& name, const std::string& path);
+
+    IImageManager* manager_;
+    std::string name_;
+    std::string path_;
+    android::base::unique_fd fd_;
+};
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/libfiemap/metadata.cpp b/libfiemap/metadata.cpp
new file mode 100644
index 0000000..597efe9
--- /dev/null
+++ b/libfiemap/metadata.cpp
@@ -0,0 +1,196 @@
+//
+// Copyright (C) 2019 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 "metadata.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <liblp/builder.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace android::fs_mgr;
+
+static constexpr uint32_t kMaxMetadataSize = 256 * 1024;
+
+std::string GetMetadataFile(const std::string& metadata_dir) {
+    return JoinPaths(metadata_dir, "lp_metadata");
+}
+
+bool MetadataExists(const std::string& metadata_dir) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    return access(metadata_file.c_str(), F_OK) == 0;
+}
+
+std::unique_ptr<LpMetadata> OpenMetadata(const std::string& metadata_dir) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    auto metadata = ReadFromImageFile(metadata_file);
+    if (!metadata) {
+        LOG(ERROR) << "Could not read metadata file " << metadata_file;
+        return nullptr;
+    }
+    return metadata;
+}
+
+// :TODO: overwrite on create if open fails
+std::unique_ptr<MetadataBuilder> OpenOrCreateMetadata(const std::string& metadata_dir,
+                                                      SplitFiemap* file) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+
+    PartitionOpener opener;
+    std::unique_ptr<MetadataBuilder> builder;
+    if (access(metadata_file.c_str(), R_OK)) {
+        if (errno != ENOENT) {
+            PLOG(ERROR) << "access " << metadata_file << " failed:";
+            return nullptr;
+        }
+
+        auto data_device = GetDevicePathForFile(file);
+
+        BlockDeviceInfo device_info;
+        if (!opener.GetInfo(data_device, &device_info)) {
+            LOG(ERROR) << "Could not read partition: " << data_device;
+            return nullptr;
+        }
+
+        std::vector<BlockDeviceInfo> block_devices = {device_info};
+        auto super_name = android::base::Basename(data_device);
+        builder = MetadataBuilder::New(block_devices, super_name, kMaxMetadataSize, 1);
+    } else {
+        auto metadata = OpenMetadata(metadata_dir);
+        if (!metadata) {
+            return nullptr;
+        }
+        builder = MetadataBuilder::New(*metadata.get(), &opener);
+    }
+
+    if (!builder) {
+        LOG(ERROR) << "Could not create metadata builder";
+        return nullptr;
+    }
+    return builder;
+}
+
+bool SaveMetadata(MetadataBuilder* builder, const std::string& metadata_dir) {
+    auto exported = builder->Export();
+    if (!exported) {
+        LOG(ERROR) << "Unable to export new metadata";
+        return false;
+    }
+
+    // If there are no more partitions in the metadata, just delete the file.
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) {
+        return true;
+    }
+    if (!WriteToImageFile(metadata_file, *exported.get())) {
+        LOG(ERROR) << "Unable to save new metadata";
+        return false;
+    }
+    return true;
+}
+
+bool RemoveAllMetadata(const std::string& dir) {
+    auto metadata_file = GetMetadataFile(dir);
+    return android::base::RemoveFileIfExists(metadata_file);
+}
+
+bool FillPartitionExtents(MetadataBuilder* builder, Partition* partition, SplitFiemap* file,
+                          uint64_t partition_size) {
+    auto block_device = android::base::Basename(GetDevicePathForFile(file));
+
+    uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+    for (const auto& extent : file->extents()) {
+        if (extent.fe_length % LP_SECTOR_SIZE != 0) {
+            LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length;
+            return false;
+        }
+        if (extent.fe_physical % LP_SECTOR_SIZE != 0) {
+            LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical;
+            return false;
+        }
+
+        uint64_t num_sectors =
+                std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed);
+        if (!num_sectors || !sectors_needed) {
+            // This should never happen, but we include it just in case. It would
+            // indicate that the last filesystem block had multiple extents.
+            LOG(WARNING) << "FiemapWriter allocated extra blocks";
+            break;
+        }
+
+        uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
+        if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) {
+            LOG(ERROR) << "Could not add extent to lp metadata";
+            return false;
+        }
+
+        sectors_needed -= num_sectors;
+    }
+    return true;
+}
+
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name) {
+    if (!MetadataExists(metadata_dir)) {
+        return true;
+    }
+    auto metadata = OpenMetadata(metadata_dir);
+    if (!metadata) {
+        return false;
+    }
+
+    PartitionOpener opener;
+    auto builder = MetadataBuilder::New(*metadata.get(), &opener);
+    if (!builder) {
+        return false;
+    }
+    builder->RemovePartition(partition_name);
+    return SaveMetadata(builder.get(), metadata_dir);
+}
+
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+                    SplitFiemap* file, uint64_t partition_size, bool readonly) {
+    auto builder = OpenOrCreateMetadata(metadata_dir, file);
+    if (!builder) {
+        return false;
+    }
+    auto partition = builder->FindPartition(partition_name);
+    if (!partition) {
+        int attrs = 0;
+        if (readonly) attrs |= LP_PARTITION_ATTR_READONLY;
+
+        if ((partition = builder->AddPartition(partition_name, attrs)) == nullptr) {
+            LOG(ERROR) << "Could not add partition " << partition_name << " to metadata";
+            return false;
+        }
+    }
+    partition->RemoveExtents();
+
+    if (!FillPartitionExtents(builder.get(), partition, file, partition_size)) {
+        return false;
+    }
+    return SaveMetadata(builder.get(), metadata_dir);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/libfiemap/metadata.h b/libfiemap/metadata.h
new file mode 100644
index 0000000..f0ce23e
--- /dev/null
+++ b/libfiemap/metadata.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2019 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <libfiemap/split_fiemap_writer.h>
+#include <liblp/liblp.h>
+
+namespace android {
+namespace fiemap {
+
+bool MetadataExists(const std::string& metadata_dir);
+std::unique_ptr<android::fs_mgr::LpMetadata> OpenMetadata(const std::string& metadata_dir);
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+                    SplitFiemap* file, uint64_t partition_size, bool readonly);
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name);
+bool RemoveAllMetadata(const std::string& dir);
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/libfiemap/utility.cpp b/libfiemap/utility.cpp
index 3dea498..1f6378f 100644
--- a/libfiemap/utility.cpp
+++ b/libfiemap/utility.cpp
@@ -17,15 +17,20 @@
 #include "utility.h"
 
 #include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <libfiemap/fiemap_writer.h>
 
 namespace android {
 namespace fiemap {
 
+static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";
+
 uint64_t DetermineMaximumFileSize(const std::string& file_path) {
     // Create the smallest file possible (one block).
     auto writer = FiemapWriter::Open(file_path, 1);
@@ -62,5 +67,27 @@
     return result;
 }
 
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(SplitFiemap* file) {
+    auto bdev_path = file->bdev_path();
+
+    struct stat userdata, given;
+    if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) {
+        if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) &&
+            given.st_rdev == userdata.st_rdev) {
+            return kUserdataDevice;
+        }
+    }
+    return bdev_path;
+}
+
+std::string JoinPaths(const std::string& dir, const std::string& file) {
+    if (android::base::EndsWith(dir, "/")) {
+        return dir + file;
+    }
+    return dir + "/" + file;
+}
+
 }  // namespace fiemap
 }  // namespace android
diff --git a/libfiemap/utility.h b/libfiemap/utility.h
index ac1ef25..19b632d 100644
--- a/libfiemap/utility.h
+++ b/libfiemap/utility.h
@@ -16,8 +16,12 @@
 
 #pragma once
 
+#include <stdint.h>
+
 #include <string>
 
+#include <libfiemap/split_fiemap_writer.h>
+
 namespace android {
 namespace fiemap {
 
@@ -26,5 +30,12 @@
 // ignored entirely.
 uint64_t DetermineMaximumFileSize(const std::string& file_path);
 
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file);
+
+// Combine two path components into a single path.
+std::string JoinPaths(const std::string& dir, const std::string& file);
+
 }  // namespace fiemap
 }  // namespace android
diff --git a/libgsi.cpp b/libgsi.cpp
index fe46145..20c9a62 100644
--- a/libgsi.cpp
+++ b/libgsi.cpp
@@ -39,7 +39,7 @@
 }
 
 bool IsGsiInstalled() {
-    return !access(kGsiInstallStatusFile, F_OK);
+    return !access(kDsuInstallStatusFile, F_OK);
 }
 
 static bool WriteAndSyncFile(const std::string& data, const std::string& file) {
@@ -74,7 +74,7 @@
         }
 
         std::string new_key;
-        if (!access(kGsiOneShotBootFile, F_OK)) {
+        if (!access(kDsuOneShotBootFile, F_OK)) {
             // Mark the GSI as disabled. This only affects the next boot, not
             // the current boot. Note that we leave the one_shot status behind.
             // This is so IGsiService can still return GSI_STATE_SINGLE_BOOT
@@ -83,7 +83,7 @@
         } else {
             new_key = std::to_string(attempts + 1);
         }
-        if (!WriteAndSyncFile(new_key, kGsiInstallStatusFile)) {
+        if (!WriteAndSyncFile(new_key, kDsuInstallStatusFile)) {
             *error = "error ("s + strerror(errno) + ")";
             return false;
         }
@@ -107,16 +107,16 @@
         return false;
     }
 
-    *metadata_file = kGsiLpMetadataFile;
+    *metadata_file = kDsuLpMetadataFile;
     return true;
 }
 
 bool UninstallGsi() {
-    return android::base::WriteStringToFile(kInstallStatusWipe, kGsiInstallStatusFile);
+    return android::base::WriteStringToFile(kInstallStatusWipe, kDsuInstallStatusFile);
 }
 
 bool DisableGsi() {
-    return android::base::WriteStringToFile(kInstallStatusDisabled, kGsiInstallStatusFile);
+    return android::base::WriteStringToFile(kInstallStatusDisabled, kDsuInstallStatusFile);
 }
 
 bool MarkSystemAsGsi() {
@@ -124,7 +124,7 @@
 }
 
 bool GetInstallStatus(std::string* status) {
-    return android::base::ReadFileToString(kGsiInstallStatusFile, status);
+    return android::base::ReadFileToString(kDsuInstallStatusFile, status);
 }
 
 bool GetBootAttempts(const std::string& boot_key, int* attempts) {
diff --git a/utility.cpp b/utility.cpp
deleted file mode 100644
index 50ac92f..0000000
--- a/utility.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-
-// Copyright (C) 2019 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 <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "file_paths.h"
-
-namespace android {
-namespace gsi {
-
-using namespace android::fiemap;
-
-// Given a SplitFiemap, this returns a device path that will work during first-
-// stage init (i.e., its path can be found by InitRequiredDevices).
-std::string GetDevicePathForFile(SplitFiemap* file) {
-    auto bdev_path = file->bdev_path();
-
-    struct stat userdata, given;
-    if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) {
-        if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) &&
-            given.st_rdev == userdata.st_rdev) {
-            return kUserdataDevice;
-        }
-    }
-    return bdev_path;
-}
-
-}  // namespace gsi
-}  // namespace android
diff --git a/utility.h b/utility.h
deleted file mode 100644
index b184d61..0000000
--- a/utility.h
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Copyright (C) 2019 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 <string>
-
-#include <libfiemap/split_fiemap_writer.h>
-
-namespace android {
-namespace gsi {
-
-// Given a SplitFiemap, this returns a device path that will work during first-
-// stage init (i.e., its path can be found by InitRequiredDevices).
-std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file);
-
-}  // namespace gsi
-}  // namespace android