diff --git a/implementations/main.cpp b/implementations/main.cpp index be86315..607e764 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -1589,6 +1589,38 @@ struct Claim { }; Claim g_claim; GDBusMethodInvocation* g_pendingClaim = nullptr; +guint g_claimWatch = 0; + +void DropClaim(const char* why) { + if (g_claimWatch) { + g_dbus_connection_signal_unsubscribe(g_conn, g_claimWatch); + g_claimWatch = 0; + } + if (g_claim.op != Op::None && g_worker) g_worker->CancelOp(); + if (g_claim.held) + std::println("claim by {} for {} dropped: {}", g_claim.sender, g_claim.user, why); + g_claim = {}; +} + +// A claim is held by a bus connection. If that connection goes away -- the +// client crashed, was killed, or simply never called Release -- the claim +// must go with it, or the device is wedged for everyone until the daemon +// restarts. fprintd watches the claimant's name for exactly this reason. Seen +// the hard way: a Claim from one busctl invocation, which exits immediately, +// left the device permanently "AlreadyInUse". +void WatchClaimant(const std::string& sender) { + g_claimWatch = g_dbus_connection_signal_subscribe( + g_conn, "org.freedesktop.DBus", "org.freedesktop.DBus", "NameOwnerChanged", + "/org/freedesktop/DBus", sender.c_str(), G_DBUS_SIGNAL_FLAGS_NONE, + [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, + GVariant* params, gpointer) { + const gchar* name = nullptr; const gchar* oldOwner = nullptr; const gchar* newOwner = nullptr; + g_variant_get(params, "(&s&s&s)", &name, &oldOwner, &newOwner); + if (g_claim.held && name && g_claim.sender == name && newOwner && *newOwner == '\0') + DropClaim("client left the bus"); + }, + nullptr, nullptr); +} std::string MapPath(std::uint32_t uid) { return fingerprintd::store::PathForUid(g_stateDir, uid); @@ -1685,7 +1717,7 @@ void PostEvent(std::unique_ptr ev) { g_claim.sender, g_claim.user, g_claim.uid, ev->templates); g_dbus_method_invocation_return_value(ev->invocation, nullptr); } else { - g_claim = {}; + DropClaim("group selection failed"); ReturnError(ev->invocation, "Internal", "could not select the user's group"); } g_pendingClaim = nullptr; @@ -1763,6 +1795,7 @@ void HandleDevice(GDBusMethodInvocation* inv, std::string_view method, GVariant* g_claim.uid = who->second; g_claim.fingers = LoadMap(g_claim.uid); g_pendingClaim = inv; + WatchClaimant(g_claim.sender); g_worker->Post(Job{ .kind = Job::Kind::Claim, .uid = g_claim.uid, .invocation = inv }); return; } @@ -1771,8 +1804,7 @@ void HandleDevice(GDBusMethodInvocation* inv, std::string_view method, GVariant* ReturnError(inv, "ClaimDevice", "device is not claimed by you"); return; } - if (g_claim.op != Op::None) g_worker->CancelOp(); - g_claim = {}; + DropClaim("released"); g_dbus_method_invocation_return_value(inv, nullptr); return; }