feat(files): unlock encrypted volumes and leave out system mount points

pull/18/head
reionwong 2 weeks ago
parent 024bc7bd6c
commit 65d5e77b0f

@ -17,6 +17,7 @@ using namespace cutefish::base;
using namespace cutefish::base::dbus;
using namespace cutefish::files;
using namespace std::chrono_literals;
using Interfaces = VolumeManager::Interfaces;
namespace {
@ -24,6 +25,9 @@ const std::string service = "org.freedesktop.UDisks2";
const std::string root = "/org/freedesktop/UDisks2";
const std::string disk = root + "/block_devices/sdb1";
const std::string drive = root + "/drives/usb";
const std::string encryptedDisk = root + "/block_devices/sdc1";
const std::string cleartextDisk = root + "/block_devices/dm_2d0";
const std::string encryptedDrive = root + "/drives/usb_encrypted";
std::vector<uint8_t> bytes(const std::string& text)
{
@ -63,6 +67,18 @@ public:
bool isReady() const { return m_ready; }
// A locked LUKS stick of its own drive; "secret" unlocks it.
void addEncrypted()
{
objects[ObjectPath{encryptedDrive}][service + ".Drive"] = {
{"Removable", Variant(true)}, {"Ejectable", Variant(true)}};
objects[ObjectPath{encryptedDisk}][service + ".Block"] = {
{"Drive", Variant(ObjectPath{encryptedDrive})},
{"PreferredDevice", Variant(bytes(std::string("/dev/sdc1\0", 10)))},
{"Size", Variant(uint64_t(4096))}};
objects[ObjectPath{encryptedDisk}][service + ".Encrypted"] = {{"CleartextDevice", Variant(ObjectPath{"/"})}};
}
template<typename F>
auto inService(F f)
{
@ -80,6 +96,8 @@ public:
VolumeManager::Objects objects;
std::vector<std::string> calls;
// Whether every call so far let the service ask for authorization.
bool interactive = true;
bool fail = false;
std::unique_ptr<Connection> connection;
@ -87,6 +105,7 @@ private:
void answer(MethodCall& call, const std::string& member, bool mount)
{
calls.push_back(member);
interactive = interactive && call.message().allowsInteractiveAuthorization();
if (call.message().signature() != "a{sv}")
call.replyError("org.freedesktop.DBus.Error.InvalidArgs", "Expected options map");
@ -138,11 +157,71 @@ private:
answer(call, "PowerOff", false);
});
ready.set_value(managed && filesystem->publish() && driveObject->publish());
auto encrypted = std::make_unique<Object>(connection.get(), encryptedDisk, service + ".Encrypted");
encrypted->addMethod("Unlock", "sa{sv}", "o", [this](MethodCall& call) {
calls.push_back("Unlock");
interactive = interactive && call.message().allowsInteractiveAuthorization();
std::string passphrase;
if (!call.read(passphrase) || passphrase != "secret") {
call.replyError("org.freedesktop.UDisks2.Error.Failed",
"Error unlocking /dev/sdc1: Failed to activate device: Operation not permitted");
return;
}
Interfaces cleartext;
cleartext[service + ".Block"] = {
{"CryptoBackingDevice", Variant(ObjectPath{encryptedDisk})},
{"PreferredDevice", Variant(bytes(std::string("/dev/dm-0\0", 10)))},
{"IdLabel", Variant(std::string("Secret"))}};
cleartext[service + ".Filesystem"] = {{"MountPoints", Variant(std::vector<std::vector<uint8_t>>())}};
objects[ObjectPath{cleartextDisk}] = cleartext;
objects[ObjectPath{encryptedDisk}][service + ".Encrypted"]["CleartextDevice"] =
Variant(ObjectPath{cleartextDisk});
connection->emitSignal(root, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded",
ObjectPath{cleartextDisk}, cleartext);
call.reply(ObjectPath{cleartextDisk});
});
encrypted->addMethod("Lock", "a{sv}", "", [this](MethodCall& call) {
answer(call, "Lock", false);
objects.erase(ObjectPath{cleartextDisk});
objects[ObjectPath{encryptedDisk}][service + ".Encrypted"]["CleartextDevice"] = Variant(ObjectPath{"/"});
connection->emitSignal(root, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved",
ObjectPath{cleartextDisk},
std::vector<std::string>{service + ".Block", service + ".Filesystem"});
});
auto cleartext = std::make_unique<Object>(connection.get(), cleartextDisk, service + ".Filesystem");
cleartext->addMethod("Mount", "a{sv}", "s", [this](MethodCall& call) {
calls.push_back("Mount");
interactive = interactive && call.message().allowsInteractiveAuthorization();
auto& filesystem = objects[ObjectPath{cleartextDisk}][service + ".Filesystem"];
filesystem["MountPoints"] =
Variant(std::vector<std::vector<uint8_t>>{bytes(std::string("/media/Secret") + '\0')});
connection->emitSignal(cleartextDisk, "org.freedesktop.DBus.Properties", "PropertiesChanged",
service + ".Filesystem", filesystem, std::vector<std::string>());
call.reply(std::string("/media/Secret"));
});
cleartext->addMethod("Unmount", "a{sv}", "", [this](MethodCall& call) {
answer(call, "Unmount", false);
auto& filesystem = objects[ObjectPath{cleartextDisk}][service + ".Filesystem"];
filesystem["MountPoints"] = Variant(std::vector<std::vector<uint8_t>>());
connection->emitSignal(cleartextDisk, "org.freedesktop.DBus.Properties", "PropertiesChanged",
service + ".Filesystem", filesystem, std::vector<std::string>());
});
auto encryptedDriveObject = std::make_unique<Object>(connection.get(), encryptedDrive, service + ".Drive");
encryptedDriveObject->addMethod("Eject", "a{sv}", "", [this](MethodCall& call) {
answer(call, "Eject", false);
});
ready.set_value(managed && filesystem->publish() && driveObject->publish() && encrypted->publish()
&& cleartext->publish() && encryptedDriveObject->publish());
loop.run();
manager.reset();
filesystem.reset();
driveObject.reset();
encrypted.reset();
cleartext.reset();
encryptedDriveObject.reset();
connection.reset();
}
@ -253,3 +332,134 @@ TEST(VolumeManagerTest, LifecycleAndOperations)
ASSERT_TRUE(waitFor(loop, [&] { return removed.size() == 2; }));
EXPECT_TRUE(manager.volumes().empty());
}
TEST(VolumeManagerTest, EncryptedVolumeUnlocksMountsAndLocks)
{
EventLoop loop;
Connection* bus = Connection::session(&loop);
if (!bus)
GTEST_SKIP() << "No session bus; run the tests in dbus-run-session";
FakeUDisks fake;
ASSERT_TRUE(fake.isReady());
fake.inService([&] { fake.addEncrypted(); });
VolumeManager manager(bus);
std::vector<std::string> rejected;
std::vector<std::tuple<std::string, std::string, std::string>> done, errors;
const Subscription s1 = manager.passphraseRejected.connect([&](const std::string& id) { rejected.push_back(id); });
const Subscription s2 = manager.operationFinished.connect(
[&](const std::string& id, const std::string& operation, const std::string& point) {
done.emplace_back(id, operation, point);
});
const Subscription s3 = manager.operationFailed.connect(
[&](const std::string& id, const std::string& operation, const std::string& message) {
errors.emplace_back(id, operation, message);
});
using files::testing::waitFor;
ASSERT_TRUE(waitFor(loop, [&] { return !manager.volume(encryptedDisk).id.empty(); }));
Volume locked = manager.volume(encryptedDisk);
EXPECT_TRUE(locked.encrypted);
EXPECT_TRUE(locked.locked());
EXPECT_FALSE(locked.filesystem);
EXPECT_TRUE(locked.name.ends_with(" Encrypted Volume")) << locked.name;
manager.mount(encryptedDisk);
ASSERT_EQ(errors.size(), 1u);
EXPECT_EQ(std::get<1>(errors.back()), "mount");
manager.unlock(encryptedDisk, "wrong");
ASSERT_TRUE(waitFor(loop, [&] { return rejected.size() == 1; }));
EXPECT_EQ(errors.size(), 1u);
EXPECT_TRUE(manager.volume(encryptedDisk).locked());
fake.inService([&] { fake.calls.clear(); });
manager.unlock(encryptedDisk, "secret");
ASSERT_TRUE(waitFor(loop, [&] { return done.size() == 1; }));
EXPECT_EQ(done.back(), std::make_tuple(encryptedDisk, std::string("unlock"), std::string("/media/Secret")));
EXPECT_EQ(fake.inService([&] { return fake.calls; }), (std::vector<std::string>{"Unlock", "Mount"}));
ASSERT_TRUE(waitFor(loop, [&] { return !manager.volume(encryptedDisk).mountPoints.empty(); }));
// The cleartext block is the encrypted volume, not one of its own.
const Volume unlocked = manager.volume(encryptedDisk);
EXPECT_EQ(unlocked.cleartext, cleartextDisk);
EXPECT_TRUE(unlocked.filesystem);
EXPECT_EQ(unlocked.name, "Secret");
EXPECT_EQ(unlocked.mountPoints, std::vector<std::string>{"/media/Secret"});
EXPECT_TRUE(manager.volume(cleartextDisk).id.empty());
EXPECT_EQ(manager.volumes().size(), 2u);
fake.inService([&] { fake.calls.clear(); });
manager.unmount(encryptedDisk);
ASSERT_TRUE(waitFor(loop, [&] { return done.size() == 2 && manager.volume(encryptedDisk).locked(); }));
EXPECT_EQ(fake.inService([&] { return fake.calls; }), (std::vector<std::string>{"Unmount", "Lock"}));
manager.unlock(encryptedDisk, "secret");
ASSERT_TRUE(waitFor(loop, [&] { return done.size() == 3 && !manager.volume(encryptedDisk).mountPoints.empty(); }));
fake.inService([&] { fake.calls.clear(); });
manager.eject(encryptedDisk);
ASSERT_TRUE(waitFor(loop, [&] { return done.size() == 4; }));
EXPECT_EQ(fake.inService([&] { return fake.calls; }), (std::vector<std::string>{"Unmount", "Lock", "Eject"}));
EXPECT_EQ(errors.size(), 1u);
EXPECT_TRUE(fake.inService([&] { return fake.interactive; }));
}
TEST(VolumeManagerTest, SystemMountPointsAreLeftOut)
{
EventLoop loop;
Connection* bus = Connection::session(&loop);
if (!bus)
GTEST_SKIP() << "No session bus; run the tests in dbus-run-session";
FakeUDisks fake;
ASSERT_TRUE(fake.isReady());
const auto mounted = [](const std::string& point) {
return VariantMap{{"MountPoints", Variant(std::vector<std::vector<uint8_t>>{bytes(point + '\0')})}};
};
const auto internal = [&](const std::string& name, bool system, const std::string& point) {
Interfaces& object = fake.objects[ObjectPath{root + "/block_devices/" + name}];
object[service + ".Block"] = {{"PreferredDevice", Variant(bytes("/dev/" + name + '\0'))},
{"IdLabel", Variant(name)},
{"HintSystem", Variant(system)},
{"Size", Variant(uint64_t(4096))}};
object[service + ".Filesystem"] = point.empty() ? VariantMap{{"MountPoints", Variant(std::vector<std::vector<uint8_t>>())}}
: mounted(point);
};
fake.inService([&] {
internal("root", true, "/");
internal("efi", true, "/boot/efi");
internal("home", true, "/home");
internal("var", true, "/var");
internal("windows", true, "");
internal("data", true, "/mnt/data");
internal("stick", false, "/run/media/user/Stick");
});
VolumeManager manager(bus);
std::vector<std::string> removed;
const Subscription s1 = manager.volumeRemoved.connect([&](const std::string& id) { removed.push_back(id); });
using files::testing::waitFor;
ASSERT_TRUE(waitFor(loop, [&] { return manager.volumes().size() == 4; }));
std::vector<std::string> names;
for (const Volume& volume : manager.volumes())
names.push_back(volume.name);
EXPECT_EQ(names, (std::vector<std::string>{"data", "USB", "stick", "windows"}));
// A Windows partition stays: it takes an administrator to mount, nothing more.
EXPECT_TRUE(manager.volume(root + "/block_devices/windows").system);
EXPECT_FALSE(manager.volume(root + "/block_devices/stick").system);
fake.inService([&] {
const std::string stick = root + "/block_devices/stick";
auto& filesystem = fake.objects[ObjectPath{stick}][service + ".Filesystem"];
filesystem = mounted("/usr");
fake.connection->emitSignal(stick, "org.freedesktop.DBus.Properties", "PropertiesChanged",
service + ".Filesystem", filesystem, std::vector<std::string>());
});
ASSERT_TRUE(waitFor(loop, [&] { return removed.size() == 1; }));
EXPECT_EQ(removed.back(), root + "/block_devices/stick");
}

@ -3,6 +3,7 @@
#include "files/volumemanager.h"
#include <algorithm>
#include <chrono>
#include "base/event_loop.h"
@ -20,6 +21,7 @@ static const std::string root = "/org/freedesktop/UDisks2";
static const std::string blockInterface = service + ".Block";
static const std::string driveInterface = service + ".Drive";
static const std::string filesystemInterface = service + ".Filesystem";
static const std::string encryptedInterface = service + ".Encrypted";
static std::string decodePath(const std::vector<uint8_t>& bytes)
{
@ -30,6 +32,20 @@ static std::string decodePath(const std::vector<uint8_t>& bytes)
return path;
}
static bool isSet(const ObjectPath& path)
{
return !path.path.empty() && path.path != "/";
}
static bool isSystemMountPoint(const std::string& path)
{
for (const std::string prefix : {"/media/", "/run/media/", "/mnt/"}) {
if (path.starts_with(prefix))
return false;
}
return path != "/mnt";
}
template<typename T>
static T value(const VariantMap& map, const std::string& key, T fallback = T())
{
@ -150,7 +166,7 @@ void VolumeManager::rebuild()
continue;
const VariantMap& block = blockFound->second;
if (value<bool>(block, "HintIgnore"))
if (value<bool>(block, "HintIgnore") || isSet(value<ObjectPath>(block, "CryptoBackingDevice")))
continue;
Volume v;
@ -164,26 +180,44 @@ void VolumeManager::rebuild()
drive = found->second;
}
const Interfaces* contents = &interfaces;
const VariantMap* cleartextBlock = nullptr;
if (const auto encrypted = interfaces.find(encryptedInterface); encrypted != interfaces.cend()) {
v.encrypted = true;
const ObjectPath cleartext = value<ObjectPath>(encrypted->second, "CleartextDevice");
const auto cleartextObject = isSet(cleartext) ? m_objects.find(cleartext) : m_objects.cend();
if (cleartextObject != m_objects.cend()) {
v.cleartext = cleartext.path;
contents = &cleartextObject->second;
if (const auto found = contents->find(blockInterface); found != contents->cend())
cleartextBlock = &found->second;
}
}
v.device = decodePath(value<std::vector<uint8_t>>(block, "PreferredDevice"));
v.size = value<uint64_t>(block, "Size");
v.filesystem = interfaces.contains(filesystemInterface);
v.filesystem = contents->contains(filesystemInterface);
v.system = value<bool>(block, "HintSystem");
v.optical = value<bool>(drive, "Optical");
v.removable = value<bool>(drive, "Removable") || value<std::string>(drive, "ConnectionBus") == "usb";
v.ejectable = value<bool>(drive, "Ejectable");
v.canPowerOff = value<bool>(drive, "CanPowerOff");
v.name = value<std::string>(block, "HintName");
if (v.name.empty() && cleartextBlock)
v.name = value<std::string>(*cleartextBlock, "IdLabel");
if (v.name.empty())
v.name = value<std::string>(block, "IdLabel");
if (v.name.empty())
v.name = v.size ? Locale::system().formattedDataSize(int64_t(v.size)) + " Volume"
v.name = v.size ? Locale::system().formattedDataSize(int64_t(v.size))
+ (v.encrypted ? " Encrypted Volume" : " Volume")
: Internal::fileNameOf(v.device);
v.icon = value<std::string>(block, "HintIconName");
if (v.icon.empty())
v.icon = v.optical ? "drive-optical" : v.removable ? "drive-removable-media" : "drive-harddisk";
if (const auto filesystem = interfaces.find(filesystemInterface); filesystem != interfaces.cend()) {
if (const auto filesystem = contents->find(filesystemInterface); filesystem != contents->cend()) {
for (const auto& point : value<std::vector<std::vector<uint8_t>>>(filesystem->second, "MountPoints")) {
const std::string mountPoint = decodePath(point);
if (!mountPoint.empty())
@ -191,6 +225,9 @@ void VolumeManager::rebuild()
}
}
if (std::ranges::any_of(v.mountPoints, isSystemMountPoint))
continue;
next.emplace(v.id, v);
}
@ -210,27 +247,74 @@ void VolumeManager::rebuild()
}
}
// The block whose filesystem a volume mounts.
static std::string filesystemPath(const Volume& volume)
{
return volume.encrypted ? volume.cleartext : volume.id;
}
void VolumeManager::mount(const std::string& id)
{
call(id, "mount", id, filesystemInterface, "Mount");
const auto v = volume(id);
if (v.locked()) {
operationFailed.emit(id, "mount", "The device is locked.");
return;
}
start(id, "mount", {{filesystemPath(v), filesystemInterface, "Mount", {}}});
}
void VolumeManager::unmount(const std::string& id)
{
call(id, "unmount", id, filesystemInterface, "Unmount");
const auto v = volume(id);
std::vector<Step> steps;
if (!v.encrypted || !v.mountPoints.empty())
steps.push_back({filesystemPath(v), filesystemInterface, "Unmount", {}});
if (v.encrypted && !v.locked())
steps.push_back({id, encryptedInterface, "Lock", {}});
start(id, "unmount", std::move(steps));
}
void VolumeManager::unlock(const std::string& id, const std::string& passphrase)
{
const auto v = volume(id);
if (!v.locked()) {
mount(id);
return;
}
start(id, "unlock", {{id, encryptedInterface, "Unlock", passphrase}, {{}, filesystemInterface, "Mount", {}}});
}
void VolumeManager::eject(const std::string& id)
{
const auto v = volume(id);
call(id, "eject", v.drive, driveInterface, v.ejectable ? "Eject" : "PowerOff");
std::vector<Step> steps;
for (const auto& [otherId, other] : m_volumes) {
if (other.drive != v.drive)
continue;
if (!other.mountPoints.empty())
steps.push_back({filesystemPath(other), filesystemInterface, "Unmount", {}});
if (other.encrypted && !other.locked())
steps.push_back({other.id, encryptedInterface, "Lock", {}});
}
if (v.ejectable || v.canPowerOff)
steps.push_back({v.drive, driveInterface, v.ejectable ? "Eject" : "PowerOff", {}});
start(id, "eject", std::move(steps));
}
void VolumeManager::call(const std::string& id, const std::string& operation, const std::string& path,
const std::string& interface, const std::string& method)
void VolumeManager::start(const std::string& id, const std::string& operation, std::vector<Step> steps)
{
const auto v = volume(id);
if (v.id.empty() || (operation == "eject" ? !(v.ejectable || v.canPowerOff) : !v.filesystem)) {
bool supported = !v.id.empty() && !steps.empty();
if (operation == "eject")
supported = supported && (v.ejectable || v.canPowerOff);
else if (operation == "mount")
supported = supported && v.filesystem;
else if (operation == "unmount")
supported = supported && (v.filesystem || v.encrypted);
else
supported = supported && v.encrypted;
if (!supported) {
operationFailed.emit(id, operation, "The device does not support this operation.");
return;
}
@ -245,61 +329,61 @@ void VolumeManager::call(const std::string& id, const std::string& operation, co
}
m_pending.insert(id);
std::vector<std::string> unmounts;
if (operation == "eject") {
for (const auto& [otherId, other] : m_volumes) {
if (other.drive == v.drive && !other.mountPoints.empty())
unmounts.push_back(other.id);
}
}
perform(id, operation, path, interface, method, unmounts, m_generation);
perform(id, operation, std::move(steps), m_generation);
}
void VolumeManager::perform(const std::string& id, const std::string& operation, const std::string& path,
const std::string& interface, const std::string& method, std::vector<std::string> unmounts,
void VolumeManager::perform(const std::string& id, const std::string& operation, std::vector<Step> steps,
uint64_t generation)
{
const bool unmounting = !unmounts.empty();
const std::string target = unmounting ? unmounts.front() : path;
if (unmounting)
unmounts.erase(unmounts.begin());
const Step step = steps.front();
steps.erase(steps.begin());
Proxy proxy(m_bus, service, target, unmounting ? filesystemInterface : interface);
Proxy proxy(m_bus, service, step.path, step.interface);
proxy.setTimeout(120s);
// An internal device takes an administrator's password to mount.
proxy.setInteractiveAuthorizationAllowed(true);
const uint64_t callId = m_nextCall++;
m_calls[callId] = proxy.callAsync(
unmounting ? "Unmount" : method,
[this, callId, id, operation, path, interface, method, unmounts, unmounting, generation](const Reply& reply) {
release(callId);
auto done = [this, callId, id, operation, step, steps, generation](const Reply& reply) mutable {
release(callId);
if (generation != m_generation || (operation != "eject" && !m_volumes.contains(id))) {
m_pending.erase(id);
operationFailed.emit(id, operation, "The device is no longer available.");
return;
}
if (generation != m_generation || (operation != "eject" && !m_volumes.contains(id))) {
m_pending.erase(id);
operationFailed.emit(id, operation, "The device is no longer available.");
return;
}
if (reply.isError()) {
m_pending.erase(id);
if (reply.isError()) {
m_pending.erase(id);
// libblockdev reports cryptsetup's EPERM for a passphrase that opens no key slot.
const bool rejected = step.method == "Unlock"
&& reply.error().message.find("Operation not permitted") != std::string::npos;
if (rejected)
passphraseRejected.emit(id);
else
operationFailed.emit(id, operation, reply.error().message);
} else if (unmounting) {
perform(id, operation, path, interface, method, unmounts, generation);
return;
} else {
m_pending.erase(id);
} else if (!steps.empty()) {
if (step.method == "Unlock" && steps.front().path.empty())
steps.front().path = reply.value<ObjectPath>().value_or(ObjectPath()).path;
perform(id, operation, std::move(steps), generation);
return;
} else {
m_pending.erase(id);
std::string mountPoint;
if (operation == "mount")
mountPoint = reply.value<std::string>().value_or(std::string());
std::string mountPoint;
if (step.method == "Mount")
mountPoint = reply.value<std::string>().value_or(std::string());
operationFinished.emit(id, operation, mountPoint);
}
operationFinished.emit(id, operation, mountPoint);
}
refresh();
},
VariantMap());
refresh();
};
if (step.passphrase)
m_calls[callId] = proxy.callAsync(step.method, std::move(done), *step.passphrase, VariantMap());
else
m_calls[callId] = proxy.callAsync(step.method, std::move(done), VariantMap());
}
} // namespace cutefish::files

@ -5,6 +5,7 @@
#include <cstdint>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
@ -25,17 +26,28 @@ struct Volume
std::string icon;
std::vector<std::string> mountPoints;
uint64_t size = 0;
// The filesystem, of the unlocked cleartext device for an encrypted volume.
bool filesystem = false;
// UDisks2's HintSystem: an internal device, which takes an administrator
// to mount.
bool system = false;
bool encrypted = false;
// The cleartext block's object path; empty while the volume is locked.
std::string cleartext;
bool optical = false;
bool removable = false;
bool ejectable = false;
bool canPowerOff = false;
bool locked() const { return encrypted && cleartext.empty(); }
bool operator==(const Volume&) const = default;
};
// UDisks2's block devices, kept up to date from its signals, and mounting,
// unmounting and ejecting them. Calls never block.
// unmounting and ejecting them. Calls never block. Blocks mounted outside
// /media, /run/media and /mnt (the root, home and boot filesystems) are left
// out, and an unlocked encrypted volume stands for its cleartext block.
class VolumeManager
{
public:
@ -50,30 +62,43 @@ public:
Volume volume(const std::string& id) const;
void mount(const std::string& id);
// Locks an encrypted volume once its filesystem is unmounted.
void unmount(const std::string& id);
// Unmounts every volume of the drive, then ejects it, or powers it off when
// it cannot be ejected.
// Unlocks an encrypted volume and mounts its filesystem; finishes as
// "unlock" with the mount point. A wrong passphrase fails with
// passphraseRejected instead of operationFailed.
void unlock(const std::string& id, const std::string& passphrase);
// Unmounts every volume of the drive and locks its encrypted ones, then
// ejects it, or powers it off when it cannot be ejected.
void eject(const std::string& id);
base::Signal<const std::string&> volumeAdded;
base::Signal<const std::string&> volumeRemoved;
base::Signal<const std::string&> volumeChanged;
// id, "mount", "unmount" or "eject", and the mount point a mount chose.
// id, "mount", "unmount", "unlock" or "eject", and the mount point a mount
// or unlock chose.
base::Signal<const std::string&, const std::string&, const std::string&> operationFinished;
base::Signal<const std::string&, const std::string&, const std::string&> operationFailed;
base::Signal<const std::string&> passphraseRejected;
base::Signal<const std::string&> errorOccurred;
using Interfaces = std::map<std::string, base::dbus::VariantMap>;
using Objects = std::map<base::dbus::ObjectPath, Interfaces>;
private:
struct Step
{
// Empty for the cleartext block the Unlock before it returned.
std::string path;
std::string interface;
std::string method;
std::optional<std::string> passphrase;
};
void refresh();
void rebuild();
void call(const std::string& id, const std::string& operation, const std::string& path,
const std::string& interface, const std::string& method);
void perform(const std::string& id, const std::string& operation, const std::string& path,
const std::string& interface, const std::string& method, std::vector<std::string> unmounts,
uint64_t generation);
void start(const std::string& id, const std::string& operation, std::vector<Step> steps);
void perform(const std::string& id, const std::string& operation, std::vector<Step> steps, uint64_t generation);
// Drops a finished call's slot once its callback has returned.
void release(uint64_t call);

Loading…
Cancel
Save