Implement suspend/resume handling

This commit is contained in:
Benjamin Berg
2021-05-05 18:10:39 +02:00
parent 66e7df1105
commit 70182083a1
4 changed files with 372 additions and 1 deletions

View File

@ -475,6 +475,81 @@ _fprint_device_get_id (FprintDevice *rdev)
return priv->id; return priv->id;
} }
static void
suspend_cb (GObject *source_obj,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GTask) task = user_data;
GError *error = NULL;
fp_device_suspend_finish (FP_DEVICE (source_obj), res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
}
static void
resume_cb (GObject *source_obj,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GTask) task = user_data;
GError *error = NULL;
fp_device_resume_finish (FP_DEVICE (source_obj), res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
}
void
fprint_device_suspend (FprintDevice *rdev,
GAsyncReadyCallback callback,
void *user_data)
{
GTask *task = NULL;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
/* Just forward to libfprint. */
task = g_task_new (rdev, NULL, callback, user_data);
fp_device_suspend (priv->dev, NULL, suspend_cb, task);
}
void
fprint_device_resume (FprintDevice *rdev,
GAsyncReadyCallback callback,
void *user_data)
{
GTask *task = NULL;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
/* Just forward to libfprint. */
task = g_task_new (rdev, NULL, callback, user_data);
fp_device_resume (priv->dev, NULL, resume_cb, task);
}
void
fprint_device_suspend_finish (FprintDevice *rdev,
GAsyncResult *res,
GError **error)
{
g_task_propagate_boolean (G_TASK (res), error);
}
void
fprint_device_resume_finish (FprintDevice *rdev,
GAsyncResult *res,
GError **error)
{
g_task_propagate_boolean (G_TASK (res), error);
}
static const char * static const char *
fp_finger_to_name (FpFinger finger) fp_finger_to_name (FpFinger finger)
{ {

View File

@ -95,6 +95,22 @@ struct _FprintDevice
FprintDevice *fprint_device_new (FpDevice *dev); FprintDevice *fprint_device_new (FpDevice *dev);
guint32 _fprint_device_get_id (FprintDevice *rdev); guint32 _fprint_device_get_id (FprintDevice *rdev);
void fprint_device_suspend (FprintDevice *rdev,
GAsyncReadyCallback callback,
void *user_data);
void fprint_device_resume (FprintDevice *rdev,
GAsyncReadyCallback callback,
void *user_data);
void fprint_device_suspend_finish (FprintDevice *rdev,
GAsyncResult *result,
GError **error);
void fprint_device_resume_finish (FprintDevice *rdev,
GAsyncResult *res,
GError **error);
/* Print */ /* Print */
/* TODO */ /* TODO */

View File

@ -24,9 +24,14 @@
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include <fprint.h> #include <fprint.h>
#include <glib-object.h> #include <glib-object.h>
#include <gio/gunixfdlist.h>
#include "fprintd.h" #include "fprintd.h"
#define LOGIND_BUS_NAME "org.freedesktop.login1"
#define LOGIND_IFACE_NAME "org.freedesktop.login1.Manager"
#define LOGIND_OBJ_PATH "/org/freedesktop/login1"
static void fprint_manager_constructed (GObject *object); static void fprint_manager_constructed (GObject *object);
static gboolean fprint_manager_get_devices (FprintManager *manager, static gboolean fprint_manager_get_devices (FprintManager *manager,
GPtrArray **devices, GPtrArray **devices,
@ -43,6 +48,9 @@ typedef struct
FpContext *context; FpContext *context;
gboolean no_timeout; gboolean no_timeout;
guint timeout_id; guint timeout_id;
gint prepare_for_sleep_pending;
guint prepare_for_sleep_id;
gint sleep_inhibit_fd;
} FprintManagerPrivate; } FprintManagerPrivate;
G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager)) G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager))
@ -60,6 +68,10 @@ fprint_manager_finalize (GObject *object)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object)); FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object));
if (priv->prepare_for_sleep_id)
g_dbus_connection_signal_unsubscribe (priv->connection,
priv->prepare_for_sleep_id);
g_clear_object (&priv->object_manager); g_clear_object (&priv->object_manager);
g_clear_object (&priv->dbus_manager); g_clear_object (&priv->dbus_manager);
g_clear_object (&priv->connection); g_clear_object (&priv->connection);
@ -227,6 +239,153 @@ handle_get_default_device (FprintManager *manager,
return TRUE; return TRUE;
} }
static void
fprint_device_suspend_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
FprintManager *manager = FPRINT_MANAGER (user_data);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
/* Fetch the result (except for the NULL dummy call). */
if (source_object != NULL)
{
fprint_device_suspend_finish (FPRINT_DEVICE (source_object), res, &error);
if (error)
{
if (!g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_OPEN) &&
!g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED))
g_message ("Unexpected error while suspending device: %s", error->message);
}
}
priv->prepare_for_sleep_pending -= 1;
/* Close FD when all devices are prepared for sleeping. */
if (priv->prepare_for_sleep_pending == 0)
{
if (priv->sleep_inhibit_fd >= 0)
close (priv->sleep_inhibit_fd);
priv->sleep_inhibit_fd = -1;
g_debug ("Released delay inhibitor for sleep.");
}
g_object_unref (manager);
}
static void
logind_sleep_inhibit_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GVariant) data = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GUnixFDList) out_fd_list = NULL;
g_autoptr(FprintManager) manager = FPRINT_MANAGER (user_data);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
gint fd_offset;
data = g_dbus_connection_call_with_unix_fd_list_finish (priv->connection, &out_fd_list, res, &error);
if (!data)
{
g_warning ("Failed to install a sleep delay inhibitor: %s", error->message);
return;
}
if (priv->sleep_inhibit_fd >= 0)
close (priv->sleep_inhibit_fd);
g_debug ("Got delay inhibitor for sleep.");
g_variant_get (data, "(h)", &fd_offset);
priv->sleep_inhibit_fd = g_unix_fd_list_get (out_fd_list, fd_offset, NULL);
}
static void
handle_prepare_for_sleep_signal (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
g_autolist (GDBusObject) devices = NULL;
FprintManager *manager = FPRINT_MANAGER (user_data);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
gboolean prepare_for_sleep;
GList *l;
if (!g_variant_check_format_string (parameters, "(b)", FALSE))
{
g_warning ("Received incorrect parameter for PrepareForSleep signal");
return;
}
g_variant_get (parameters, "(b)", &prepare_for_sleep);
/* called one more time to handle the case of no devices */
if (prepare_for_sleep)
priv->prepare_for_sleep_pending = 1;
devices = g_dbus_object_manager_get_objects (priv->object_manager);
g_debug ("Preparing devices for %s", prepare_for_sleep ? "sleep" : "resume");
for (l = devices; l != NULL; l = l->next)
{
g_autoptr(FprintDevice) dev = NULL;
FprintDBusObjectSkeleton *object = l->data;
dev = fprint_dbus_object_skeleton_get_device (object);
if (prepare_for_sleep)
{
priv->prepare_for_sleep_pending += 1;
g_object_ref (manager);
fprint_device_suspend (dev, fprint_device_suspend_cb, manager);
}
else
{
fprint_device_resume (dev, NULL, NULL);
}
}
if (prepare_for_sleep)
{
/* "Notify" the initial dummy device we added, handling no devices that suspending */
g_object_ref (manager);
fprint_device_suspend_cb (NULL, NULL, manager);
}
else
{
GVariant *arg = NULL;
arg = g_variant_new ("(ssss)",
"sleep",
"net.reactivated.Fprint",
"Suspend fingerprint readers",
"delay");
/* Grab a sleep inhibitor. */
g_dbus_connection_call_with_unix_fd_list (priv->connection,
LOGIND_BUS_NAME,
LOGIND_OBJ_PATH,
LOGIND_IFACE_NAME,
"Inhibit",
arg,
G_VARIANT_TYPE ("(h)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL,
logind_sleep_inhibit_cb,
g_object_ref (manager));
}
}
static void static void
device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context) device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context)
{ {
@ -290,6 +449,7 @@ device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context)
static void static void
fprint_manager_constructed (GObject *object) fprint_manager_constructed (GObject *object)
{ {
g_autoptr(GVariant) param_false = NULL;
FprintManager *manager = FPRINT_MANAGER (object); FprintManager *manager = FPRINT_MANAGER (object);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
GDBusObjectManagerServer *object_manager_server; GDBusObjectManagerServer *object_manager_server;
@ -319,6 +479,20 @@ fprint_manager_constructed (GObject *object)
g_dbus_object_manager_server_set_connection (object_manager_server, g_dbus_object_manager_server_set_connection (object_manager_server,
priv->connection); priv->connection);
priv->prepare_for_sleep_id = g_dbus_connection_signal_subscribe (priv->connection,
LOGIND_BUS_NAME,
LOGIND_IFACE_NAME,
"PrepareForSleep",
LOGIND_OBJ_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
handle_prepare_for_sleep_signal,
manager,
NULL);
/* Fake a resume as that triggers the inhibitor to be taken. */
param_false = g_variant_new ("(b)", FALSE);
handle_prepare_for_sleep_signal (priv->connection, NULL, NULL, NULL, NULL, param_false, manager);
/* And register the signals for initial enumeration and hotplug. */ /* And register the signals for initial enumeration and hotplug. */
g_signal_connect_object (priv->context, g_signal_connect_object (priv->context,
"device-added", "device-added",

View File

@ -247,10 +247,15 @@ class FPrintdTest(dbusmock.DBusTestCase):
argv.insert(2, '--suppressions=%s' % valgrind) argv.insert(2, '--suppressions=%s' % valgrind)
self.valgrind = True self.valgrind = True
self.kill_daemon = False self.kill_daemon = False
self.daemon_log = OutputChecker()
self.daemon = subprocess.Popen(argv, self.daemon = subprocess.Popen(argv,
env=env, env=env,
stdout=None, stdout=self.daemon_log.fd,
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.daemon_log.writer_attached()
#subprocess.Popen(['/usr/bin/dbus-monitor', '--system'])
self.addCleanup(self.daemon_stop) self.addCleanup(self.daemon_stop)
timeout_count = timeout * 10 timeout_count = timeout * 10
@ -310,6 +315,8 @@ class FPrintdTest(dbusmock.DBusTestCase):
else: else:
raise(e) raise(e)
self.daemon_log.assert_closed()
if not self.kill_daemon: if not self.kill_daemon:
self.assertLess(self.daemon.returncode, 128) self.assertLess(self.daemon.returncode, 128)
self.assertGreaterEqual(self.daemon.returncode, 0) self.assertGreaterEqual(self.daemon.returncode, 0)
@ -591,8 +598,22 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
self.manager = None self.manager = None
self.device = None self.device = None
self.polkitd_start() self.polkitd_start()
fifo_path = os.path.join(self.tmpdir, 'logind_inhibit_fifo')
os.mkfifo(fifo_path)
self.logind_inhibit_fifo = os.open(fifo_path, os.O_RDONLY | os.O_NONBLOCK | os.O_CLOEXEC)
# EOF without a writer, BlockingIOError with a writer
self.assertFalse(self.holds_inhibitor())
self.logind, self.logind_obj = self.spawn_server_template('logind', { })
self.logind_obj.AddMethod('org.freedesktop.login1.Manager', 'Inhibit', 'ssss', 'h',
'ret = os.open("%s", os.O_WRONLY)\n' % fifo_path +
'from gi.repository import GLib\n' +
'GLib.idle_add(lambda fd: os.close(fd), ret)')
self.daemon_start(self.driver_name) self.daemon_start(self.driver_name)
self.wait_got_delay_inhibitor()
if self.device is None: if self.device is None:
self.skipTest("Need {} device to run the test".format(self.device_driver)) self.skipTest("Need {} device to run the test".format(self.device_driver))
@ -641,6 +662,8 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
self.device = None self.device = None
self.manager = None self.manager = None
os.close(self.logind_inhibit_fifo)
super().tearDown() super().tearDown()
def try_release(self): def try_release(self):
@ -672,6 +695,23 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
if expected is not None: if expected is not None:
self.assertEqual(self._last_result, expected) self.assertEqual(self._last_result, expected)
def holds_inhibitor(self):
try:
if os.read(self.logind_inhibit_fifo, 1) == b'':
return False
except BlockingIOError:
return True
raise AssertionError("logind inhibitor fifo in unexpected state")
def wait_got_delay_inhibitor(self, timeout=0):
self.daemon_log.check_line('Got delay inhibitor for sleep', timeout=timeout)
self.assertTrue(self.holds_inhibitor())
def wait_released_delay_inhibitor(self, timeout=0):
self.daemon_log.check_line('Released delay inhibitor for sleep', timeout=timeout)
self.assertFalse(self.holds_inhibitor())
def enroll_image(self, img, device=None, finger='right-index-finger', def enroll_image(self, img, device=None, finger='right-index-finger',
expected_result='enroll-completed', claim_user=None, expected_result='enroll-completed', claim_user=None,
start=True, stop=True): start=True, stop=True):
@ -1538,6 +1578,72 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
self.assertIn(GLib.Variant('()', ()), self.get_all_async_replies()) self.assertIn(GLib.Variant('()', ()), self.get_all_async_replies())
def test_suspend_inhibit_unclaimed(self):
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
self.wait_released_delay_inhibitor(timeout=1)
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
self.wait_got_delay_inhibitor(timeout=1)
def test_suspend_inhibit_claimed(self):
self.device.Claim('(s)', 'testuser')
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
self.wait_released_delay_inhibitor(timeout=1)
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
self.wait_got_delay_inhibitor(timeout=1)
self.device.Release()
def test_suspend_inhibit_cancels_enroll(self):
self.device.Claim('(s)', 'testuser')
self.device.EnrollStart('(s)', 'right-thumb')
# Now prepare for sleep, which will trigger an internal cancellation
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
self.wait_for_result(expected='enroll-unknown-error')
self.wait_released_delay_inhibitor(timeout=1)
self.assertEqual(os.read(self.logind_inhibit_fifo, 1), b'')
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
self.wait_got_delay_inhibitor(timeout=1)
self.device.Release()
def test_suspend_prevents_enroll(self):
self.device.Claim('(s)', 'testuser')
# Now prepare for sleep, which will trigger an internal cancellation
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
self.wait_released_delay_inhibitor(timeout=1)
self.device.EnrollStart('(s)', 'right-thumb')
self.wait_for_result(expected='enroll-unknown-error')
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
self.wait_got_delay_inhibitor(timeout=1)
self.device.Release()
class FPrintdVirtualDeviceStorageTest(FPrintdVirtualStorageDeviceBaseTest, class FPrintdVirtualDeviceStorageTest(FPrintdVirtualStorageDeviceBaseTest,
FPrintdVirtualDeviceTest): FPrintdVirtualDeviceTest):