cryptohome: Add logical volumes as a backing device

Conditionally add logical volume support as a backing device for
cryptohomes. In subsequent CLs, we use these devices as a backing
store for dm-crypt based cryptohomes.

BUG=b:172344853
TEST=unittests

Cq-Depend: chromium:2593050
Change-Id: Ia277e2999d4ced6997a723959f378f7aa95b4480
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2579890
Reviewed-by: Leo Lai <cylai@google.com>
Reviewed-by: Daniil Lunev <dlunev@chromium.org>
Tested-by: Sarthak Kukreti <sarthakkukreti@chromium.org>
Commit-Queue: Sarthak Kukreti <sarthakkukreti@chromium.org>
diff --git a/cryptohome/BUILD.gn b/cryptohome/BUILD.gn
index 2939a03..a8c06cf 100644
--- a/cryptohome/BUILD.gn
+++ b/cryptohome/BUILD.gn
@@ -551,6 +551,12 @@
       deps += [ ":cert_provision-static" ]
     }
 
+    if (use.lvm_stateful_partition) {
+      sources += [
+        "storage/encrypted_container/logical_volume_backing_device_test.cc",
+      ]
+    }
+
     # TODO(crbug.com/1082873): Remove after fixing usage of deprecated
     # declarations.
     cflags_cc = [ "-Wno-error=deprecated-declarations" ]
diff --git a/cryptohome/libs/BUILD.gn b/cryptohome/libs/BUILD.gn
index 931d9eb..cc16899 100644
--- a/cryptohome/libs/BUILD.gn
+++ b/cryptohome/libs/BUILD.gn
@@ -13,6 +13,7 @@
 
 config("target_defaults") {
   defines = [
+    "USE_LVM_STATEFUL_PARTITION=${use.lvm_stateful_partition}",
     "USE_PINWEAVER=${use.pinweaver}",
     "USE_SELINUX=${use.selinux}",
     "USE_TPM2=${use.tpm2}",
@@ -157,6 +158,13 @@
   if (use.selinux) {
     libs += [ "selinux" ]
   }
+
+  defines = [ "USE_LVM_STATEFUL_PARTIITON=${use.lvm_stateful_partition}" ]
+  if (use.lvm_stateful_partition) {
+    sources +=
+        [ "../storage/encrypted_container/logical_volume_backing_device.cc" ]
+  }
+
   all_dependent_configs = [ ":libcrosplatform_dependent_config" ]
 
   deps = [ ":cryptohome-proto" ]
diff --git a/cryptohome/storage/encrypted_container/backing_device.cc b/cryptohome/storage/encrypted_container/backing_device.cc
index 9886d9f..9c9524b 100644
--- a/cryptohome/storage/encrypted_container/backing_device.cc
+++ b/cryptohome/storage/encrypted_container/backing_device.cc
@@ -6,8 +6,11 @@
 
 #include <memory>
 
-#include "cryptohome/storage/encrypted_container/loopback_device.h"
 #include "cryptohome/platform.h"
+#include "cryptohome/storage/encrypted_container/loopback_device.h"
+#if USE_LVM_STATEFUL_PARTITION
+#include "cryptohome/storage/encrypted_container/logical_volume_backing_device.h"
+#endif  // USE_LVM_STATEFUL_PARTITION
 
 namespace cryptohome {
 
@@ -18,6 +21,10 @@
     case BackingDeviceType::kLoopbackDevice:
       return std::make_unique<LoopbackDevice>(
           config, platform, std::make_unique<brillo::LoopDeviceManager>());
+#if USE_LVM_STATEFUL_PARTITION
+    case BackingDeviceType::kLogicalVolumeBackingDevice:
+      return std::make_unique<LogicalVolumeBackingDevice>(config);
+#endif  // USE_LVM_STATEFUL_PARTITION
     default:
       return nullptr;
   }
diff --git a/cryptohome/storage/encrypted_container/backing_device.h b/cryptohome/storage/encrypted_container/backing_device.h
index daf964a..cac53a0 100644
--- a/cryptohome/storage/encrypted_container/backing_device.h
+++ b/cryptohome/storage/encrypted_container/backing_device.h
@@ -20,6 +20,7 @@
 enum class BackingDeviceType {
   kUnknown = 0,
   kLoopbackDevice,
+  kLogicalVolumeBackingDevice,
 };
 
 // Configuration for backing devices.
@@ -30,6 +31,10 @@
   struct {
     base::FilePath backing_file_path;
   } loopback;
+  struct {
+    std::string thinpool_name;
+    base::FilePath physical_volume;
+  } logical_volume;
 };
 
 // `BackingDevice` represents a backing block device that can be used as a
diff --git a/cryptohome/storage/encrypted_container/logical_volume_backing_device.cc b/cryptohome/storage/encrypted_container/logical_volume_backing_device.cc
new file mode 100644
index 0000000..27fd562
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/logical_volume_backing_device.cc
@@ -0,0 +1,129 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cryptohome/storage/encrypted_container/logical_volume_backing_device.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <base/files/file_path.h>
+#include <base/values.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/blkdev_utils/lvm.h>
+#include <brillo/blkdev_utils/lvm_device.h>
+
+#include "cryptohome/storage/encrypted_container/backing_device.h"
+
+namespace cryptohome {
+
+LogicalVolumeBackingDevice::LogicalVolumeBackingDevice(
+    const BackingDeviceConfig& config,
+    std::unique_ptr<brillo::LogicalVolumeManager> lvm)
+    : name_(config.name),
+      size_(config.size),
+      physical_volume_(config.logical_volume.physical_volume),
+      thinpool_name_(config.logical_volume.thinpool_name),
+      lvm_(std::move(lvm)) {}
+
+LogicalVolumeBackingDevice::LogicalVolumeBackingDevice(
+    const BackingDeviceConfig& config)
+    : LogicalVolumeBackingDevice(
+          config, std::make_unique<brillo::LogicalVolumeManager>()) {}
+
+base::Optional<brillo::LogicalVolume>
+LogicalVolumeBackingDevice::GetLogicalVolume() {
+  base::Optional<brillo::PhysicalVolume> pv =
+      lvm_->GetPhysicalVolume(physical_volume_);
+
+  if (!pv || !pv->IsValid()) {
+    return base::nullopt;
+  }
+
+  base::Optional<brillo::VolumeGroup> vg = lvm_->GetVolumeGroup(*pv);
+
+  if (!vg || !vg->IsValid()) {
+    return base::nullopt;
+  }
+
+  return lvm_->GetLogicalVolume(*vg, name_);
+}
+
+bool LogicalVolumeBackingDevice::Purge() {
+  base::Optional<brillo::LogicalVolume> lv = GetLogicalVolume();
+
+  if (lv && lv->IsValid()) {
+    return lv->Remove();
+  }
+
+  return false;
+}
+
+bool LogicalVolumeBackingDevice::Create() {
+  base::Optional<brillo::PhysicalVolume> pv =
+      lvm_->GetPhysicalVolume(physical_volume_);
+
+  if (!pv || !pv->IsValid()) {
+    return false;
+  }
+
+  base::Optional<brillo::VolumeGroup> vg = lvm_->GetVolumeGroup(*pv);
+
+  if (!vg || !vg->IsValid()) {
+    return false;
+  }
+
+  base::Optional<brillo::Thinpool> thinpool =
+      lvm_->GetThinpool(*vg, thinpool_name_);
+  if (!thinpool || !thinpool->IsValid()) {
+    return false;
+  }
+
+  base::DictionaryValue lv_config;
+  lv_config.SetString("name", name_);
+  lv_config.SetString("size", base::NumberToString(size_));
+
+  base::Optional<brillo::LogicalVolume> lv =
+      lvm_->CreateLogicalVolume(*vg, *thinpool, lv_config);
+  if (!lv || !lv->IsValid()) {
+    return false;
+  }
+
+  return true;
+}
+
+bool LogicalVolumeBackingDevice::Setup() {
+  base::Optional<brillo::LogicalVolume> lv = GetLogicalVolume();
+
+  if (!lv || !lv->IsValid()) {
+    LOG(ERROR) << "Failed to set up logical volume.";
+    return false;
+  }
+
+  return lv->Activate();
+}
+
+bool LogicalVolumeBackingDevice::Teardown() {
+  base::Optional<brillo::LogicalVolume> lv = GetLogicalVolume();
+
+  if (!lv || !lv->IsValid()) {
+    LOG(ERROR) << "Invalid logical volume";
+    return false;
+  }
+
+  return lv->Deactivate();
+}
+
+base::Optional<base::FilePath> LogicalVolumeBackingDevice::GetPath() {
+  base::Optional<brillo::LogicalVolume> lv = GetLogicalVolume();
+
+  if (!lv || !lv->IsValid()) {
+    LOG(ERROR) << "Invalid logical volume";
+    return base::nullopt;
+  }
+
+  return lv->GetPath();
+}
+
+}  // namespace cryptohome
diff --git a/cryptohome/storage/encrypted_container/logical_volume_backing_device.h b/cryptohome/storage/encrypted_container/logical_volume_backing_device.h
new file mode 100644
index 0000000..66fd7b9
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/logical_volume_backing_device.h
@@ -0,0 +1,67 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_LOGICAL_VOLUME_BACKING_DEVICE_H_
+#define CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_LOGICAL_VOLUME_BACKING_DEVICE_H_
+
+#include "cryptohome/storage/encrypted_container/backing_device.h"
+
+#include <memory>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/values.h>
+#include <brillo/blkdev_utils/lvm.h>
+
+namespace cryptohome {
+
+// `LogicalVolumeBackingDevice` represents a thin volume backing device.
+class LogicalVolumeBackingDevice : public BackingDevice {
+ public:
+  // `LogicalVolumeBackingDevice` are defined by the following config values:
+  // - `name`: Name of the logical volume.
+  // - `thinpool_name`: Name of thinpool on which the logical volume resides.
+  // - `physical_volume`: Name of device on which the logical volume should be
+  //                      set up.
+  // - `size`: Size of thin logical volume.
+  LogicalVolumeBackingDevice(const BackingDeviceConfig& config,
+                             std::unique_ptr<brillo::LogicalVolumeManager> lvm);
+  explicit LogicalVolumeBackingDevice(const BackingDeviceConfig& config);
+  ~LogicalVolumeBackingDevice() = default;
+
+  // Creates the thin logical volume.
+  bool Create() override;
+
+  // Removed the thin logical volume. The volume should not be in-use before
+  // calling this function.
+  bool Purge() override;
+
+  // Activates the logical volume.
+  bool Setup() override;
+
+  // Deactivates the logical volume.
+  bool Teardown() override;
+
+  // Gets the device type for reporting.
+  BackingDeviceType GetType() override {
+    return BackingDeviceType::kLogicalVolumeBackingDevice;
+  }
+
+  // Gets path to the logical volume's block device.
+  base::Optional<base::FilePath> GetPath() override;
+
+ private:
+  base::Optional<brillo::LogicalVolume> GetLogicalVolume();
+
+  const std::string name_;
+  const uint64_t size_;
+  const base::FilePath physical_volume_;
+  const std::string thinpool_name_;
+
+  std::unique_ptr<brillo::LogicalVolumeManager> lvm_;
+};
+
+}  // namespace cryptohome
+
+#endif  // CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_LOGICAL_VOLUME_BACKING_DEVICE_H_
diff --git a/cryptohome/storage/encrypted_container/logical_volume_backing_device_test.cc b/cryptohome/storage/encrypted_container/logical_volume_backing_device_test.cc
new file mode 100644
index 0000000..376ffa6
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/logical_volume_backing_device_test.cc
@@ -0,0 +1,133 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cryptohome/storage/encrypted_container/logical_volume_backing_device.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <base/files/file_util.h>
+#include <brillo/blkdev_utils/mock_lvm.h>
+#include <brillo/blkdev_utils/lvm_device.h>
+
+#include "cryptohome/storage/encrypted_container/backing_device.h"
+
+namespace cryptohome {
+
+namespace {
+constexpr char kPhysicalVolumeReport[] =
+    "{\"report\": [{ \"pv\": [ {\"pv_name\":\"/dev/mmcblk0p1\", "
+    "\"vg_name\":\"stateful\"}]}]}";
+constexpr char kThinpoolReport[] =
+    "{\"report\": [{ \"lv\": [ {\"lv_name\":\"thinpool\", "
+    "\"vg_name\":\"stateful\"}]}]}";
+constexpr char kLogicalVolumeReport[] =
+    "{\"report\": [{ \"lv\": [ {\"lv_name\":\"foo\", "
+    "\"vg_name\":\"stateful\"}]}]}";
+}  // namespace
+
+class LogicalVolumeBackingDeviceTest : public ::testing::Test {
+ public:
+  LogicalVolumeBackingDeviceTest()
+      : config_({.type = BackingDeviceType::kLogicalVolumeBackingDevice,
+                 .name = "foo",
+                 .size = 1024,
+                 .logical_volume = {.thinpool_name = "thinpool",
+                                    .physical_volume =
+                                        base::FilePath("/dev/mmcblk0p1")}}),
+        lvm_command_runner_(std::make_shared<brillo::MockLvmCommandRunner>()),
+        backing_device_(std::make_unique<LogicalVolumeBackingDevice>(
+            config_,
+            std::make_unique<brillo::LogicalVolumeManager>(
+                lvm_command_runner_))) {}
+  ~LogicalVolumeBackingDeviceTest() override = default;
+
+  void ExpectVolumeGroup() {
+    std::vector<std::string> pvdisplay = {
+        "/sbin/pvdisplay", "-C", "--reportformat", "json",
+        config_.logical_volume.physical_volume.value()};
+    EXPECT_CALL(*lvm_command_runner_.get(), RunProcess(pvdisplay, _))
+        .WillRepeatedly(
+            DoAll(SetArgPointee<1>(std::string(kPhysicalVolumeReport)),
+                  Return(true)));
+  }
+
+  void ExpectThinpool() {
+    std::vector<std::string> thinpool_display = {
+        "/sbin/lvdisplay", "-S",   "pool_lv=\"\"",     "-C",
+        "--reportformat",  "json", "stateful/thinpool"};
+    EXPECT_CALL(*lvm_command_runner_.get(), RunProcess(thinpool_display, _))
+        .WillRepeatedly(DoAll(SetArgPointee<1>(std::string(kThinpoolReport)),
+                              Return(true)));
+  }
+  void ExpectLogicalVolume() {
+    std::vector<std::string> thinpool_display = {
+        "/sbin/lvdisplay", "-S",   "pool_lv!=\"\"",           "-C",
+        "--reportformat",  "json", "stateful/" + config_.name};
+    EXPECT_CALL(*lvm_command_runner_.get(), RunProcess(thinpool_display, _))
+        .WillRepeatedly(DoAll(
+            SetArgPointee<1>(std::string(kLogicalVolumeReport)), Return(true)));
+  }
+
+ protected:
+  BackingDeviceConfig config_;
+  std::shared_ptr<brillo::MockLvmCommandRunner> lvm_command_runner_;
+
+  std::unique_ptr<BackingDevice> backing_device_;
+};
+
+TEST_F(LogicalVolumeBackingDeviceTest, LogicalVolumeDeviceSetup) {
+  ExpectVolumeGroup();
+  ExpectLogicalVolume();
+
+  std::vector<std::string> lv_enable = {"lvchange", "-ay", "stateful/foo"};
+  EXPECT_CALL(*lvm_command_runner_.get(), RunCommand(lv_enable))
+      .Times(1)
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(backing_device_->Setup());
+}
+
+TEST_F(LogicalVolumeBackingDeviceTest, LogicalVolumeDeviceCreate) {
+  ExpectVolumeGroup();
+  ExpectThinpool();
+
+  std::vector<std::string> lv_create = {
+      "lvcreate",   "--thin",           "-V", "1024M", "-n",
+      config_.name, "stateful/thinpool"};
+  EXPECT_CALL(*lvm_command_runner_.get(), RunCommand(lv_create))
+      .Times(1)
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(backing_device_->Create());
+}
+
+TEST_F(LogicalVolumeBackingDeviceTest, LogicalVolumeDeviceTeardown) {
+  ExpectVolumeGroup();
+  ExpectLogicalVolume();
+
+  std::vector<std::string> lv_disable = {"lvchange", "-an", "stateful/foo"};
+  EXPECT_CALL(*lvm_command_runner_.get(), RunCommand(lv_disable))
+      .Times(1)
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(backing_device_->Teardown());
+}
+
+TEST_F(LogicalVolumeBackingDeviceTest, LogicalVolumeDevicePurge) {
+  ExpectVolumeGroup();
+  ExpectLogicalVolume();
+
+  std::vector<std::string> lv_disable = {"lvremove", "stateful/foo"};
+  EXPECT_CALL(*lvm_command_runner_.get(), RunCommand(lv_disable))
+      .Times(1)
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(backing_device_->Purge());
+}
+
+}  // namespace cryptohome