// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 - 2016 Red Hat, Inc.
* Author: Matthias Clasen <mclasen@redhat.com>
*/
#include "nm-default.h"
#include "nm-sleep-monitor.h"
#include <sys/stat.h>
#include <gio/gunixfdlist.h>
#include "nm-core-internal.h"
#include "NetworkManagerUtils.h"
#if defined (SUSPEND_RESUME_UPOWER)
#define SUSPEND_DBUS_NAME "org.freedesktop.UPower"
#define SUSPEND_DBUS_PATH "/org/freedesktop/UPower"
#define SUSPEND_DBUS_INTERFACE "org.freedesktop.UPower"
#define USE_UPOWER 1
#define _NMLOG_PREFIX_NAME "sleep-monitor-up"
#elif defined (SUSPEND_RESUME_SYSTEMD) || defined (SUSPEND_RESUME_ELOGIND)
#define SUSPEND_DBUS_NAME "org.freedesktop.login1"
#define SUSPEND_DBUS_PATH "/org/freedesktop/login1"
#define SUSPEND_DBUS_INTERFACE "org.freedesktop.login1.Manager"
#define USE_UPOWER 0
#if defined (SUSPEND_RESUME_SYSTEMD)
#define _NMLOG_PREFIX_NAME "sleep-monitor-sd"
#else
#define _NMLOG_PREFIX_NAME "sleep-monitor-el"
#endif
#elif defined(SUSPEND_RESUME_CONSOLEKIT)
/* ConsoleKit2 has added the same suspend/resume DBUS API that Systemd
* uses. http://consolekit2.github.io/ConsoleKit2/#Manager.Inhibit
*/
#define SUSPEND_DBUS_NAME "org.freedesktop.ConsoleKit"
#define SUSPEND_DBUS_PATH "/org/freedesktop/ConsoleKit/Manager"
#define SUSPEND_DBUS_INTERFACE "org.freedesktop.ConsoleKit.Manager"
#define USE_UPOWER 0
#define _NMLOG_PREFIX_NAME "sleep-monitor-ck"
#else
#error define one of SUSPEND_RESUME_SYSTEMD, SUSPEND_RESUME_ELOGIND, SUSPEND_RESUME_CONSOLEKIT, or SUSPEND_RESUME_UPOWER
#endif
/*****************************************************************************/
enum {
SLEEPING,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
struct _NMSleepMonitor {
GObject parent;
GDBusProxy *proxy;
/* used both during construction of proxy and during Inhibit call. */
GCancellable *cancellable;
int inhibit_fd;
GSList *handles_active;
GSList *handles_stale;
gulong sig_id_1;
gulong sig_id_2;
};
struct _NMSleepMonitorClass {
GObjectClass parent;
};
G_DEFINE_TYPE (NMSleepMonitor, nm_sleep_monitor, G_TYPE_OBJECT);
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_SUSPEND
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, _NMLOG_PREFIX_NAME, __VA_ARGS__)
/*****************************************************************************/
static void sleep_signal (NMSleepMonitor *self, gboolean is_about_to_suspend);
/*****************************************************************************/
#if USE_UPOWER
static void
upower_sleeping_cb (GDBusProxy *proxy, gpointer user_data)
{
sleep_signal (user_data, TRUE);
}
static void
upower_resuming_cb (GDBusProxy *proxy, gpointer user_data)
{
sleep_signal (user_data, FALSE);
}
#else /* USE_UPOWER */
static void
drop_inhibitor (NMSleepMonitor *self, gboolean force)
{
if (!force && self->handles_active)
return;
if (self->inhibit_fd >= 0) {
_LOGD ("inhibit: dropping sleep inhibitor %d", self->inhibit_fd);
nm_close (self->inhibit_fd);
self->inhibit_fd = -1;
}
if (self->handles_active) {
self->handles_stale = g_slist_concat (self->handles_stale, self->handles_active);
self->handles_active = NULL;
}
nm_clear_g_cancellable (&self->cancellable);
}
static void
inhibit_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusProxy *proxy = G_DBUS_PROXY (source);
NMSleepMonitor *self = user_data;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *res = NULL;
gs_unref_object GUnixFDList *fd_list = NULL;
res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
if (!res) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_clear_object (&self->cancellable);
_LOGW ("inhibit: failed (%s)", error->message);
}
return;
}
g_clear_object (&self->cancellable);
if (!fd_list || g_unix_fd_list_get_length (fd_list) != 1) {
_LOGW ("inhibit: didn't get a single fd back");
return;
}
self->inhibit_fd = g_unix_fd_list_get (fd_list, 0, NULL);
_LOGD ("inhibit: inhibitor fd is %d", self->inhibit_fd);
}
static void
take_inhibitor (NMSleepMonitor *self)
{
g_return_if_fail (NM_IS_SLEEP_MONITOR (self));
g_return_if_fail (G_IS_DBUS_PROXY (self->proxy));
drop_inhibitor (self, TRUE);
_LOGD ("inhibit: taking sleep inhibitor...");
self->cancellable = g_cancellable_new ();
g_dbus_proxy_call_with_unix_fd_list (self->proxy,
"Inhibit",
g_variant_new ("(ssss)",
"sleep",
"NetworkManager",
"NetworkManager needs to turn off networks",
"delay"),
0,
G_MAXINT,
NULL,
self->cancellable,
inhibit_done,
self);
}
static void
prepare_for_sleep_cb (GDBusProxy *proxy,
gboolean is_about_to_suspend,
gpointer data)
{
sleep_signal (data, is_about_to_suspend);
}
static void
name_owner_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GDBusProxy *proxy = G_DBUS_PROXY (object);
NMSleepMonitor *self = NM_SLEEP_MONITOR (user_data);
char *owner;
g_assert (proxy == self->proxy);
owner = g_dbus_proxy_get_name_owner (proxy);
if (owner)
take_inhibitor (self);
else
drop_inhibitor (self, TRUE);
g_free (owner);
}
#endif /* USE_UPOWER */
static void
sleep_signal (NMSleepMonitor *self,
gboolean is_about_to_suspend)
{
g_return_if_fail (NM_IS_SLEEP_MONITOR (self));
_LOGD ("received %s signal", is_about_to_suspend ? "SLEEP" : "RESUME");
#if !USE_UPOWER
if (!is_about_to_suspend)
take_inhibitor (self);
#endif
g_signal_emit (self, signals[SLEEPING], 0, is_about_to_suspend);
#if !USE_UPOWER
if (is_about_to_suspend)
drop_inhibitor (self, FALSE);
#endif
}
/**
* nm_sleep_monitor_inhibit_take:
* @self: the #NMSleepMonitor instance
*
* Prevent the release of inhibitor lock
*
* Returns: an inhibitor handle that must be returned via
* nm_sleep_monitor_inhibit_release().
**/
NMSleepMonitorInhibitorHandle *
nm_sleep_monitor_inhibit_take (NMSleepMonitor *self)
{
g_return_val_if_fail (NM_IS_SLEEP_MONITOR (self), NULL);
self->handles_active = g_slist_prepend (self->handles_active, NULL);
return (NMSleepMonitorInhibitorHandle *) self->handles_active;
}
/**
* nm_sleep_monitor_inhibit_release:
* @self: the #NMSleepMonitor instance
* @handle: the #NMSleepMonitorInhibitorHandle inhibitor handle.
*
* Allow again the release of inhibitor lock
**/
void
nm_sleep_monitor_inhibit_release (NMSleepMonitor *self,
NMSleepMonitorInhibitorHandle *handle)
{
GSList *l;
g_return_if_fail (NM_IS_SLEEP_MONITOR (self));
g_return_if_fail (handle);
l = (GSList *) handle;
if (g_slist_position (self->handles_active, l) < 0) {
if (g_slist_position (self->handles_stale, l) < 0)
g_return_if_reached ();
self->handles_stale = g_slist_delete_link (self->handles_stale, l);
return;
}
self->handles_active = g_slist_delete_link (self->handles_active, l);
#if !USE_UPOWER
drop_inhibitor (self, FALSE);
#endif
}
static void
on_proxy_acquired (GObject *object,
GAsyncResult *res,
NMSleepMonitor *self)
{
GError *error = NULL;
GDBusProxy *proxy;
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (!proxy) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
_LOGW ("failed to acquire D-Bus proxy: %s", error->message);
g_clear_error (&error);
return;
}
self->proxy = proxy;
g_clear_object (&self->cancellable);
#if USE_UPOWER
self->sig_id_1 = _nm_dbus_signal_connect (self->proxy, "Sleeping", NULL,
G_CALLBACK (upower_sleeping_cb), self);
self->sig_id_2 = _nm_dbus_signal_connect (self->proxy, "Resuming", NULL,
G_CALLBACK (upower_resuming_cb), self);
#else
self->sig_id_1 = g_signal_connect (self->proxy, "notify::g-name-owner",
G_CALLBACK (name_owner_cb), self);
self->sig_id_2 = _nm_dbus_signal_connect (self->proxy, "PrepareForSleep",
G_VARIANT_TYPE ("(b)"),
G_CALLBACK (prepare_for_sleep_cb), self);
{
gs_free char *owner = NULL;
owner = g_dbus_proxy_get_name_owner (self->proxy);
if (owner)
take_inhibitor (self);
}
#endif
}
/*****************************************************************************/
static void
nm_sleep_monitor_init (NMSleepMonitor *self)
{
self->inhibit_fd = -1;
self->cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
SUSPEND_DBUS_NAME, SUSPEND_DBUS_PATH, SUSPEND_DBUS_INTERFACE,
self->cancellable,
(GAsyncReadyCallback) on_proxy_acquired, self);
}
NMSleepMonitor *
nm_sleep_monitor_new (void)
{
return g_object_new (NM_TYPE_SLEEP_MONITOR, NULL);
}
static void
dispose (GObject *object)
{
NMSleepMonitor *self = NM_SLEEP_MONITOR (object);
#if !USE_UPOWER
drop_inhibitor (self, TRUE);
#endif
nm_clear_g_cancellable (&self->cancellable);
if (self->proxy) {
nm_clear_g_signal_handler (self->proxy, &self->sig_id_1);
nm_clear_g_signal_handler (self->proxy, &self->sig_id_2);
g_clear_object (&self->proxy);
}
G_OBJECT_CLASS (nm_sleep_monitor_parent_class)->dispose (object);
}
static void
nm_sleep_monitor_class_init (NMSleepMonitorClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = dispose;
signals[SLEEPING] = g_signal_new (NM_SLEEP_MONITOR_SLEEPING,
NM_TYPE_SLEEP_MONITOR,
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}