[gnome-settings-daemon/benzea/oom-notify] housekeeping: Add systemd OOM notification module




commit 86a4085a47cc30276c5f6ef1480e1c5f00f33f28
Author: Benjamin Berg <bberg redhat com>
Date:   Thu Apr 28 15:06:53 2022 +0200

    housekeeping: Add systemd OOM notification module
    
    Note that we only notify if a unit (scope/service) is actually stopped
    due to OOM. This will not catch certain other cases where e.g. a
    delegated cgroup receives an OOM event.

 plugins/housekeeping/gsd-housekeeping-manager.c |   7 +
 plugins/housekeeping/gsd-systemd-notify.c       | 259 ++++++++++++++++++++++++
 plugins/housekeeping/gsd-systemd-notify.h       |  32 +++
 plugins/housekeeping/meson.build                |   1 +
 4 files changed, 299 insertions(+)
---
diff --git a/plugins/housekeeping/gsd-housekeeping-manager.c b/plugins/housekeeping/gsd-housekeeping-manager.c
index 8ad985c8..b291667a 100644
--- a/plugins/housekeeping/gsd-housekeeping-manager.c
+++ b/plugins/housekeeping/gsd-housekeeping-manager.c
@@ -26,6 +26,7 @@
 #include "gnome-settings-profile.h"
 #include "gsd-housekeeping-manager.h"
 #include "gsd-disk-space.h"
+#include "gsd-systemd-notify.h"
 
 
 /* General */
@@ -59,6 +60,8 @@ struct _GsdHousekeepingManager {
         GDBusConnection *connection;
         GCancellable    *bus_cancellable;
         guint            name_id;
+
+        GsdSystemdNotify *systemd_notify;
 };
 
 static void     gsd_housekeeping_manager_class_init  (GsdHousekeepingManagerClass *klass);
@@ -431,6 +434,8 @@ gsd_housekeeping_manager_start (GsdHousekeepingManager *manager,
                                       manager);
         g_source_set_name_by_id (manager->long_term_cb, "[gnome-settings-daemon] do_cleanup");
 
+        manager->systemd_notify = g_object_new (GSD_TYPE_SYSTEMD_NOTIFY, NULL);
+
         gnome_settings_profile_end (NULL);
 
         return TRUE;
@@ -450,6 +455,8 @@ gsd_housekeeping_manager_stop (GsdHousekeepingManager *manager)
         g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
         g_clear_object (&manager->connection);
 
+        g_clear_object (&manager->systemd_notify);
+
         if (manager->short_term_cb) {
                 g_source_remove (manager->short_term_cb);
                 manager->short_term_cb = 0;
diff --git a/plugins/housekeeping/gsd-systemd-notify.c b/plugins/housekeeping/gsd-systemd-notify.c
new file mode 100644
index 00000000..f944f289
--- /dev/null
+++ b/plugins/housekeeping/gsd-systemd-notify.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 Benjamin Berg <bberg redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+
+#include "gsd-systemd-notify.h"
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <libnotify/notify.h>
+
+struct _GsdSystemdNotify {
+        GObject parent;
+
+        GDBusConnection *session;
+        guint sub_service;
+        guint sub_scope;
+};
+
+G_DEFINE_TYPE (GsdSystemdNotify, gsd_systemd_notify, G_TYPE_OBJECT)
+
+static void
+notify_oom_kill (char *unit)
+{
+        g_autoptr(GDesktopAppInfo) app = NULL;
+        g_autofree char *unit_copy = NULL;
+        g_autofree char *app_id = NULL;
+        g_autofree char *desktop_id = NULL;
+        g_autofree char *summary = NULL;
+        g_autofree char *message = NULL;
+        NotifyNotification *notification = NULL;
+        char *pos;
+
+        unit_copy = g_strdup (unit);
+
+        if (g_str_has_suffix (unit_copy, ".service")) {
+                /* Find (first) @ character */
+                pos = strchr (unit_copy, '@');
+                if (pos)
+                        *pos = '\0';
+        } else if (g_str_has_suffix (unit_copy, ".scope")) {
+                /* Find last - character */
+                pos = strrchr (unit_copy, '-');
+                if (pos)
+                        *pos = '\0';
+        } else {
+                /* This cannot happen, because we only subscribe to the Scope
+                 * and Service DBus interfaces.
+                 */
+                g_assert_not_reached ();
+                return;
+        }
+
+
+        pos = strrchr (unit_copy, '-');
+        if (pos) {
+                pos += 1;
+
+                app_id = g_strcompress (pos);
+                desktop_id = g_strjoin (NULL, app_id, ".desktop", NULL);
+
+                app = g_desktop_app_info_new (desktop_id);
+        }
+
+        if (app) {
+                /* TRANSLATORS: %s is the application name. */
+                summary = g_strdup_printf (_("%s was killed due to low memory"),
+                                           g_app_info_get_name (G_APP_INFO (app)));
+                /* TRANSLATORS: %s is the application name. */
+                message = g_strdup_printf (_("The system was running low on memory. \"%s\" was killed to 
avoid further issues."),
+                                           g_app_info_get_name (G_APP_INFO (app)));
+        } else if (g_str_has_prefix (unit, "vte-spawn-")) {
+                /* TRANSLATORS: A terminal tab/window was killed. */
+                summary = g_strdup_printf (_("A terminal window was killed due to low memory"));
+                /* TRANSLATORS: A terminal tab/window was killed. */
+                message = g_strdup_printf (_("The system was running low on memory. A terminal window was 
killed to avoid further issues."));
+        } else {
+                /* TRANSLATORS: We don't have a good description of what was killed. */
+                summary = g_strdup_printf (_("An application was killed due to low memory"));
+                /* TRANSLATORS: We don't have a good description of what was killed. */
+                message = g_strdup_printf (_("The system was running low on memory. An application was 
killed to avoid further issues."));
+        }
+
+        notification = notify_notification_new (summary, message, "dialog-warning-symbolic");
+
+        if (app) {
+                notify_notification_set_hint_string (notification, "desktop-entry", desktop_id);
+                notify_notification_set_app_name (notification, g_app_info_get_name (G_APP_INFO (app)));
+        }
+        notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE));
+        notify_notification_set_urgency (notification, NOTIFY_URGENCY_CRITICAL);
+        notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
+
+        notify_notification_show (notification, NULL);
+        g_object_unref (notification);
+}
+
+/* Taken from hexdecoct.c in systemd, LGPL-2.1-or-later */
+static int
+unhexchar (char c)
+{
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+
+        if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+
+        return -EINVAL;
+}
+
+
+static char*
+unescape_dbus_path (const char *path)
+{
+        g_autofree char *res = g_malloc (strlen (path) + 1);
+        char *r;
+
+        for (r = res; *path; path += 1, r += 1) {
+                int c1, c2;
+                if (*path != '_') {
+                        *r = *path;
+                        continue;
+                }
+                /* Read next two hex characters */
+                path += 1;
+                c1 = unhexchar (*path);
+                if (c1 < 0)
+                        return NULL;
+                path += 1;
+                c2 = unhexchar (*path);
+                if (c2 < 0)
+                        return NULL;
+
+                *r = (c1 << 4) | c2;
+        }
+        *r = '\0';
+
+        return g_steal_pointer (&res);
+}
+
+static void
+on_unit_properties_changed (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
+{
+        g_autoptr(GVariant) dict = NULL;
+        const char *result = NULL;
+        const char *unit_escaped = NULL;
+        g_autofree char *unit = NULL;
+
+        g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")));
+
+        dict = g_variant_get_child_value (parameters, 1);
+        g_assert (dict);
+
+        unit_escaped = strrchr (object_path, '/');
+        g_assert (unit_escaped);
+        unit_escaped += 1;
+
+        unit = unescape_dbus_path (unit_escaped);
+        g_assert (unit);
+
+        if (g_variant_lookup (dict, "Result", "&s", &result)) {
+                if (g_strcmp0 (result, "oom-kill") == 0)
+                        notify_oom_kill (unit);
+        }
+}
+
+static void
+on_bus_gotten (GDBusConnection  *obj,
+               GAsyncResult     *res,
+               GsdSystemdNotify *self)
+{
+        g_autoptr(GError) error = NULL;
+        GDBusConnection *con;
+
+        con = g_bus_get_finish (res, &error);
+        if (!con) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get session bus: %s", error->message);
+                return;
+        }
+
+        self->session = con;
+        self->sub_service = g_dbus_connection_signal_subscribe (self->session,
+                                                                "org.freedesktop.systemd1",
+                                                                "org.freedesktop.DBus.Properties",
+                                                                "PropertiesChanged",
+                                                                NULL,
+                                                                "org.freedesktop.systemd1.Service",
+                                                                G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+                                                                on_unit_properties_changed,
+                                                                self,
+                                                                NULL);
+
+        self->sub_scope = g_dbus_connection_signal_subscribe (self->session,
+                                                              "org.freedesktop.systemd1",
+                                                              "org.freedesktop.DBus.Properties",
+                                                              "PropertiesChanged",
+                                                              NULL,
+                                                              "org.freedesktop.systemd1.Scope",
+                                                              G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+                                                              on_unit_properties_changed,
+                                                              self,
+                                                              NULL);
+}
+
+static void
+gsd_systemd_notify_init (GsdSystemdNotify *self)
+{
+        g_bus_get (G_BUS_TYPE_SESSION, NULL, (GAsyncReadyCallback) on_bus_gotten, self);
+}
+
+static void
+gsd_systemd_notify_dispose (GObject *obj)
+{
+        GsdSystemdNotify *self = GSD_SYSTEMD_NOTIFY (obj);
+
+        if (self->sub_service) {
+                g_dbus_connection_signal_unsubscribe (self->session, self->sub_service);
+                g_dbus_connection_signal_unsubscribe (self->session, self->sub_scope);
+        }
+        self->sub_service = 0;
+        self->sub_scope = 0;
+        g_clear_object (&self->session);
+
+        G_OBJECT_CLASS (gsd_systemd_notify_parent_class)->dispose (obj);
+}
+
+static void
+gsd_systemd_notify_class_init (GsdSystemdNotifyClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->dispose = gsd_systemd_notify_dispose;
+}
+
diff --git a/plugins/housekeeping/gsd-systemd-notify.h b/plugins/housekeeping/gsd-systemd-notify.h
new file mode 100644
index 00000000..51560d1e
--- /dev/null
+++ b/plugins/housekeeping/gsd-systemd-notify.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 Benjamin Berg <bberg redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SYSTEMD_NOTIFY_H
+#define __GSD_SYSTEMD_NOTIFY_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SYSTEMD_NOTIFY         (gsd_systemd_notify_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdSystemdNotify, gsd_systemd_notify, GSD, SYSTEMD_NOTIFY, GObject)
+
+G_END_DECLS
+
+#endif /* __GSD_SYSTEMD_NOTIFY_H */
diff --git a/plugins/housekeeping/meson.build b/plugins/housekeeping/meson.build
index a0b4ca5f..ce7ba81b 100644
--- a/plugins/housekeeping/meson.build
+++ b/plugins/housekeeping/meson.build
@@ -5,6 +5,7 @@ common_files = files(
 
 sources = common_files + files(
   'gsd-housekeeping-manager.c',
+  'gsd-systemd-notify.c',
   'main.c'
 )
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]