/* 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-power-monitor.h" #include #include #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-core-intern/nm-core-internal.h" #include "NetworkManagerUtils.h" #if 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" #if defined(SUSPEND_RESUME_SYSTEMD) #define _NMLOG_PREFIX_NAME "power-monitor-sd" #else #define _NMLOG_PREFIX_NAME "power-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 _NMLOG_PREFIX_NAME "power-monitor-ck" #else #error define one of SUSPEND_RESUME_SYSTEMD, SUSPEND_RESUME_ELOGIND, SUSPEND_RESUME_CONSOLEKIT #endif /*****************************************************************************/ enum { SLEEPING, SHUTDOWN, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; struct _NMPowerMonitor { 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; gulong sig_id_3; }; struct _NMPowerMonitorClass { GObjectClass parent; }; G_DEFINE_TYPE(NMPowerMonitor, nm_power_monitor, G_TYPE_OBJECT); /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_SUSPEND #define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, _NMLOG_PREFIX_NAME, __VA_ARGS__) /*****************************************************************************/ static void drop_inhibitor(NMPowerMonitor *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); NMPowerMonitor *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(NMPowerMonitor *self) { g_return_if_fail(NM_IS_POWER_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 name_owner_cb(GObject *object, GParamSpec *pspec, gpointer user_data) { GDBusProxy *proxy = G_DBUS_PROXY(object); NMPowerMonitor *self = NM_POWER_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); } static void prepare_for_sleep_cb(GDBusProxy *proxy, gboolean is_about_to_suspend, NMPowerMonitor *self) { g_return_if_fail(NM_IS_POWER_MONITOR(self)); _LOGD("received %s signal", is_about_to_suspend ? "SLEEP" : "RESUME"); if (!is_about_to_suspend) take_inhibitor(self); g_signal_emit(self, signals[SLEEPING], 0, is_about_to_suspend); if (is_about_to_suspend) drop_inhibitor(self, FALSE); } static void prepare_for_shutdown_cb(GDBusProxy *proxy, gboolean is_about_to_shutdown, NMPowerMonitor *self) { g_return_if_fail(NM_IS_POWER_MONITOR(self)); _LOGD("received SHUTDOWN signal"); g_signal_emit(self, signals[SHUTDOWN], 0); } /** * nm_power_monitor_inhibit_take: * @self: the #NMPowerMonitor instance * * Prevent the release of inhibitor lock * * Returns: an inhibitor handle that must be returned via * nm_power_monitor_inhibit_release(). **/ NMPowerMonitorInhibitorHandle * nm_power_monitor_inhibit_take(NMPowerMonitor *self) { g_return_val_if_fail(NM_IS_POWER_MONITOR(self), NULL); self->handles_active = g_slist_prepend(self->handles_active, NULL); return (NMPowerMonitorInhibitorHandle *) self->handles_active; } /** * nm_power_monitor_inhibit_release: * @self: the #NMPowerMonitor instance * @handle: the #NMPowerMonitorInhibitorHandle inhibitor handle. * * Allow again the release of inhibitor lock **/ void nm_power_monitor_inhibit_release(NMPowerMonitor *self, NMPowerMonitorInhibitorHandle *handle) { GSList *l; g_return_if_fail(NM_IS_POWER_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); drop_inhibitor(self, FALSE); } static void on_proxy_acquired(GObject *object, GAsyncResult *res, NMPowerMonitor *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); 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_proxy_signal_connect(self->proxy, "PrepareForSleep", G_VARIANT_TYPE("(b)"), G_CALLBACK(prepare_for_sleep_cb), self); self->sig_id_3 = _nm_dbus_proxy_signal_connect(self->proxy, "PrepareForShutdown", G_VARIANT_TYPE("(b)"), G_CALLBACK(prepare_for_shutdown_cb), self); { gs_free char *owner = NULL; owner = g_dbus_proxy_get_name_owner(self->proxy); if (owner) take_inhibitor(self); } } /*****************************************************************************/ static void nm_power_monitor_init(NMPowerMonitor *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); } NMPowerMonitor * nm_power_monitor_new(void) { return g_object_new(NM_TYPE_POWER_MONITOR, NULL); } static void dispose(GObject *object) { NMPowerMonitor *self = NM_POWER_MONITOR(object); drop_inhibitor(self, TRUE); 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); nm_clear_g_signal_handler(self->proxy, &self->sig_id_3); g_clear_object(&self->proxy); } G_OBJECT_CLASS(nm_power_monitor_parent_class)->dispose(object); } static void nm_power_monitor_class_init(NMPowerMonitorClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = dispose; signals[SLEEPING] = g_signal_new(NM_POWER_MONITOR_SLEEPING, NM_TYPE_POWER_MONITOR, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); signals[SHUTDOWN] = g_signal_new(NM_POWER_MONITOR_SHUTDOWN, NM_TYPE_POWER_MONITOR, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); }