Add CreateEmptyDevice and WaitForDevice APIs

These APIs support a flow in which dm devices can be created before they
are actually needed, hence minimizing the time a process will wait for
ueventd to create user space paths.

Bug: 190618831
Test: atest libdm_test
Change-Id: I4dfa14e5271a6a13de6da73ec3c7efb1ebc0f8b8
This commit is contained in:
Nikita Ioffe 2021-06-25 19:48:10 +01:00
parent 5e2363e14d
commit 15e0f5a98a
3 changed files with 53 additions and 7 deletions

View File

@ -170,19 +170,18 @@ static bool IsRecovery() {
return access("/system/bin/recovery", F_OK) == 0;
}
bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path,
const std::chrono::milliseconds& timeout_ms) {
bool DeviceMapper::CreateEmptyDevice(const std::string& name) {
std::string uuid = GenerateUuid();
if (!CreateDevice(name, uuid)) {
return false;
}
return CreateDevice(name, uuid);
}
bool DeviceMapper::WaitForDevice(const std::string& name,
const std::chrono::milliseconds& timeout_ms, std::string* path) {
// We use the unique path for testing whether the device is ready. After
// that, it's safe to use the dm-N path which is compatible with callers
// that expect it to be formatted as such.
std::string unique_path;
if (!LoadTableAndActivate(name, table) || !GetDeviceUniquePath(name, &unique_path) ||
!GetDmDevicePathByName(name, path)) {
if (!GetDeviceUniquePath(name, &unique_path) || !GetDmDevicePathByName(name, path)) {
DeleteDevice(name);
return false;
}
@ -208,6 +207,25 @@ bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, s
return true;
}
bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path,
const std::chrono::milliseconds& timeout_ms) {
if (!CreateEmptyDevice(name)) {
return false;
}
if (!LoadTableAndActivate(name, table)) {
DeleteDevice(name);
return false;
}
if (!WaitForDevice(name, timeout_ms, path)) {
DeleteDevice(name);
return false;
}
return true;
}
bool DeviceMapper::GetDeviceUniquePath(const std::string& name, std::string* path) {
struct dm_ioctl io;
InitIo(&io, name);

View File

@ -29,6 +29,7 @@
#include <thread>
#include <android-base/file.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
@ -679,3 +680,17 @@ TEST(libdm, DeleteDeviceDeferredWaitsForLastReference) {
ASSERT_NE(0, access(path.c_str(), F_OK));
ASSERT_EQ(ENOENT, errno);
}
TEST(libdm, CreateEmptyDevice) {
DeviceMapper& dm = DeviceMapper::Instance();
ASSERT_TRUE(dm.CreateEmptyDevice("empty-device"));
auto guard = android::base::make_scope_guard([&]() { dm.DeleteDevice("empty-device", 5s); });
// Empty device should be in suspended state.
ASSERT_EQ(DmDeviceState::SUSPENDED, dm.GetState("empty-device"));
std::string path;
ASSERT_TRUE(dm.WaitForDevice("empty-device", 5s, &path));
// Path should exist.
ASSERT_EQ(0, access(path.c_str(), F_OK));
}

View File

@ -115,6 +115,19 @@ class DeviceMapper final {
// - ACTIVE: resumes the device.
bool ChangeState(const std::string& name, DmDeviceState state);
// Creates empty device.
// This supports a use case when a caller doesn't need a device straight away, but instead
// asks kernel to create it beforehand, thus avoiding blocking itself from waiting for ueventd
// to create user space paths.
// Callers are expected to then activate their device by calling LoadTableAndActivate function.
// To avoid race conditions, callers must still synchronize with ueventd by calling
// WaitForDevice function.
bool CreateEmptyDevice(const std::string& name);
// Waits for device paths to be created in the user space.
bool WaitForDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
std::string* path);
// Creates a device, loads the given table, and activates it. If the device
// is not able to be activated, it is destroyed, and false is returned.
// After creation, |path| contains the result of calling