libfiemap_writer: Handle dm stacking more accurately.
Right now we completely unwind a device-mapper stacking, but this will produce incorrect results if the stack doesn't have a 1:1 block mapping. For example, extents against a dynamic partition will be invalid if used against the underlying block device (super). In this case, abort the device-mapper unwinding early, and leave it to the caller to decide if the device is valid. In addition to fixing a potentially serious bug, this will allow us to test gsid by simulating various device types (FBE vs FDE, for example). Bug: 134536978 Test: fiemap_writer_test gtest Change-Id: I8164e2b71c4868f5494e17e08d209b65fa0ed4ef
This commit is contained in:
parent
e53417a8ee
commit
d5fa799f76
3 changed files with 61 additions and 7 deletions
|
@ -30,6 +30,7 @@ cc_library_static {
|
|||
],
|
||||
|
||||
static_libs: [
|
||||
"libdm",
|
||||
"libext4_utils",
|
||||
],
|
||||
|
||||
|
|
|
@ -37,10 +37,13 @@
|
|||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <libdm/dm.h>
|
||||
|
||||
namespace android {
|
||||
namespace fiemap_writer {
|
||||
|
||||
using namespace android::dm;
|
||||
|
||||
// We are expecting no more than 512 extents in a fiemap of the file we create.
|
||||
// If we find more, then it is treated as error for now.
|
||||
static constexpr const uint32_t kMaxExtents = 512;
|
||||
|
@ -87,14 +90,57 @@ static bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_
|
|||
}
|
||||
|
||||
static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
|
||||
// TODO: Stop popping the device mapper stack if dm-linear target is found
|
||||
*bdev_raw = bdev;
|
||||
|
||||
if (!::android::base::StartsWith(bdev, "dm-")) {
|
||||
// We are at the bottom of the device mapper stack.
|
||||
*bdev_raw = bdev;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string dm_leaf_dir = ::android::base::StringPrintf("/sys/block/%s/slaves", bdev.c_str());
|
||||
// Get the device name.
|
||||
auto dm_name_file = "/sys/block/" + bdev + "/dm/name";
|
||||
std::string dm_name;
|
||||
if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
|
||||
PLOG(ERROR) << "Could not read file: " << dm_name_file;
|
||||
return false;
|
||||
}
|
||||
dm_name = android::base::Trim(dm_name);
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
std::vector<DeviceMapper::TargetInfo> table;
|
||||
if (!dm.GetTableInfo(dm_name, &table)) {
|
||||
LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The purpose of libfiemap_writer is to provide an extent-based view into
|
||||
// a file. This is difficult if devices are not layered in a 1:1 manner;
|
||||
// we would have to translate and break up extents based on the actual
|
||||
// block mapping. Since this is too complex, we simply stop processing
|
||||
// the device-mapper stack if we encounter a complex case.
|
||||
//
|
||||
// It is up to the caller to decide whether stopping at a virtual block
|
||||
// device is allowable. In most cases it is not, because we want either
|
||||
// "userdata" or an external volume. It is useful for tests however.
|
||||
// Callers can check by comparing the device number to that of userdata,
|
||||
// or by checking whether is a device-mapper node.
|
||||
if (table.size() > 1) {
|
||||
LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev;
|
||||
return true;
|
||||
}
|
||||
const auto& entry = table[0].spec;
|
||||
std::string target_type(std::string(entry.target_type, sizeof(entry.target_type)).c_str());
|
||||
if (target_type != "bow" && target_type != "default-key" && target_type != "crypt") {
|
||||
LOG(INFO) << "Stopping at complex target-type " << target_type << " for " << dm_name
|
||||
<< " at " << bdev;
|
||||
return true;
|
||||
}
|
||||
if (entry.sector_start != 0) {
|
||||
LOG(INFO) << "Stopping at target-type with non-zero starting sector";
|
||||
return true;
|
||||
}
|
||||
|
||||
auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves";
|
||||
auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
|
||||
if (d == nullptr) {
|
||||
PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
|
||||
|
|
|
@ -60,10 +60,17 @@ class FiemapWriter final {
|
|||
// FiemapWriter::Open).
|
||||
static bool HasPinnedExtents(const std::string& file_path);
|
||||
|
||||
// Returns the underlying block device of a file. This will look past device-mapper layers.
|
||||
// If an intermediate device-mapper layer would not maintain a 1:1 mapping (i.e. is a non-
|
||||
// trivial dm-linear), then this will fail. If device-mapper nodes are encountered, then
|
||||
// |uses_dm| will be set to true.
|
||||
// Returns the underlying block device of a file. This will look past device-mapper layers
|
||||
// as long as each layer would not change block mappings (i.e., dm-crypt, dm-bow, and dm-
|
||||
// default-key tables are okay; dm-linear is not). If a mapping such as dm-linear is found,
|
||||
// it will be returned in place of any physical block device.
|
||||
//
|
||||
// It is the caller's responsibility to check whether the returned block device is acceptable.
|
||||
// Gsid, for example, will only accept /dev/block/by-name/userdata as the bottom device.
|
||||
// Callers can check the device name (dm- or loop prefix), inspect sysfs, or compare the major
|
||||
// number against a boot device.
|
||||
//
|
||||
// If device-mapper nodes were encountered, then |uses_dm| will be set to true.
|
||||
static bool GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
|
||||
bool* uses_dm = nullptr);
|
||||
|
||||
|
|
Loading…
Reference in a new issue