From 671bd812cfa6454c0d9b55ace53c3d1ecd176cba Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 24 Jan 2020 19:19:27 -0800 Subject: [PATCH] Fix mounting /data on Virtual A/B devices without metadata encryption. When using partitions backed by /data, for example during a Virtual A/B merge or "adb remount" on a Virtual A/B device, the userdata block device is seen as in-use when /data mounts in second-stage init. This subsequently prevents mount() or e2fsck from working. Metadata-encrypted devices are not affected, because dm-default-key provides a wrapping block device that can be used exclusively. This patch addresses the problem by detecting when userdata has device-mapper dependencies. If it does, and the device is not metadata-encrypted, we introduce a dm-linear wrapper around userdata. It is created on demand, but like logical partitions, it exists until the device reboots. Bug: 134949511 Test: adb remount, cuttlefish boots Change-Id: Ifbfea1591a6e58978fdaffd6ef889afabd10e270 --- fs_mgr/fs_mgr.cpp | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp index ed8206603..181e8ba45 100644 --- a/fs_mgr/fs_mgr.cpp +++ b/fs_mgr/fs_mgr.cpp @@ -100,6 +100,7 @@ using android::base::Timer; using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; +using android::dm::DmTargetLinear; // Realistically, this file should be part of the android::fs_mgr namespace; using namespace android::fs_mgr; @@ -1070,6 +1071,83 @@ std::string fs_mgr_find_bow_device(const std::string& block_device) { } } +static constexpr const char* kUserdataWrapperName = "userdata-wrapper"; + +static void WrapUserdata(FstabEntry* entry, dev_t dev, const std::string& block_device) { + DeviceMapper& dm = DeviceMapper::Instance(); + if (dm.GetState(kUserdataWrapperName) != DmDeviceState::INVALID) { + // This will report failure for us. If we do fail to get the path, + // we leave the device unwrapped. + dm.GetDmDevicePathByName(kUserdataWrapperName, &entry->blk_device); + return; + } + + unique_fd fd(open(block_device.c_str(), O_RDONLY | O_CLOEXEC)); + if (fd < 0) { + PLOG(ERROR) << "open failed: " << entry->blk_device; + return; + } + + auto dev_str = android::base::StringPrintf("%u:%u", major(dev), minor(dev)); + uint64_t sectors = get_block_device_size(fd) / 512; + + android::dm::DmTable table; + table.Emplace(0, sectors, dev_str, 0); + + std::string dm_path; + if (!dm.CreateDevice(kUserdataWrapperName, table, &dm_path, 20s)) { + LOG(ERROR) << "Failed to create userdata wrapper device"; + return; + } + entry->blk_device = dm_path; +} + +// When using Virtual A/B, partitions can be backed by /data and mapped with +// device-mapper in first-stage init. This can happen when merging an OTA or +// when using adb remount to house "scratch". In this case, /data cannot be +// mounted directly off the userdata block device, and e2fsck will refuse to +// scan it, because the kernel reports the block device as in-use. +// +// As a workaround, when mounting /data, we create a trivial dm-linear wrapper +// if the underlying block device already has dependencies. Note that we make +// an exception for metadata-encrypted devices, since dm-default-key is already +// a wrapper. +static void WrapUserdataIfNeeded(FstabEntry* entry, const std::string& actual_block_device = {}) { + const auto& block_device = + actual_block_device.empty() ? entry->blk_device : actual_block_device; + if (entry->mount_point != "/data" || !entry->key_dir.empty() || + android::base::StartsWith(block_device, "/dev/block/dm-")) { + return; + } + + struct stat st; + if (stat(block_device.c_str(), &st) < 0) { + PLOG(ERROR) << "stat failed: " << block_device; + return; + } + + std::string path = android::base::StringPrintf("/sys/dev/block/%u:%u/holders", + major(st.st_rdev), minor(st.st_rdev)); + std::unique_ptr dir(opendir(path.c_str()), closedir); + if (!dir) { + PLOG(ERROR) << "opendir failed: " << path; + return; + } + + struct dirent* d; + bool has_holders = false; + while ((d = readdir(dir.get())) != nullptr) { + if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { + has_holders = true; + break; + } + } + + if (has_holders) { + WrapUserdata(entry, st.st_rdev, block_device); + } +} + static bool IsMountPointMounted(const std::string& mount_point) { // Check if this is already mounted. Fstab fstab; @@ -1149,6 +1227,8 @@ int fs_mgr_mount_all(Fstab* fstab, int mount_mode) { } } + WrapUserdataIfNeeded(¤t_entry); + if (!checkpoint_manager.Update(¤t_entry)) { continue; } @@ -1419,6 +1499,9 @@ int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) { } std::string block_device; if (auto entry = GetEntryForMountPoint(&proc_mounts, "/data"); entry != nullptr) { + // Note: we don't care about a userdata wrapper here, since it's safe + // to remount on top of the bow device instead, there will be no + // conflicts. block_device = entry->blk_device; } else { LERROR << "/data is not mounted"; @@ -1537,6 +1620,8 @@ static int fs_mgr_do_mount_helper(Fstab* fstab, const std::string& n_name, } } + WrapUserdataIfNeeded(&fstab_entry, n_blk_device); + if (!checkpoint_manager.Update(&fstab_entry, n_blk_device)) { LERROR << "Could not set up checkpoint partition, skipping!"; continue;