/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2012 - 2016 Red Hat, Inc. * Author: Matthias Clasen */ #include "src/core/nm-default-daemon.h" #include "nm-sleep-monitor.h" #include #include #include "libnm-core-intern/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); }