[gnome-control-center/wip/user-identities: 5/7] user-accounts: add alarm class



commit 39ccafca1daf06f044d4a06b76c72184740f5aad
Author: Ray Strode <rstrode redhat com>
Date:   Mon Feb 27 16:22:39 2012 -0500

    user-accounts: add alarm class
    
    When we support showing kerberos realms in the UI,
    we're going to need some way to get notified when
    the users credentials are no longer valid for
    those realms.
    
    This commit adds an alarm class that abstracts
    timerfd so we can get proper notification when
    kerberos credentials expire.

 configure.ac                     |   53 ++++
 panels/user-accounts/Makefile.am |    2 +
 panels/user-accounts/um-alarm.c  |  490 ++++++++++++++++++++++++++++++++++++++
 panels/user-accounts/um-alarm.h  |   66 +++++
 4 files changed, 611 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 7d33013..798cdca 100644
--- a/configure.ac
+++ b/configure.ac
@@ -234,6 +234,59 @@ AC_SUBST(PANEL_LIBS)
 PANEL_LDFLAGS="-export_dynamic -avoid-version -module -no-undefined -export-symbols-regex '^g_io_module_(load|unload)'"
 AC_SUBST(PANEL_LDFLAGS)
 
+dnl ==================================================
+dnl timerfd support
+dnl ==================================================
+AC_MSG_CHECKING([for timerfd support])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#include <sys/timerfd.h>
+#include <unistd.h>
+],[
+int
+main (void)
+{
+  struct itimerspec timer_spec = { 0 };
+  timerfd_settime (timerfd_create (CLOCK_MONOTONIC, TFD_CLOEXEC),
+                   TFD_TIMER_ABSTIME,
+                   &timer_spec,
+                   NULL);
+
+  return 0;
+}
+])],
+[have_timerfd=yes],
+[have_timerfd=no])
+AC_MSG_RESULT($have_timerfd)
+if test x"$have_timerfd" = x"yes"; then
+    AC_DEFINE(HAVE_TIMERFD, 1, [have timerfd support])
+
+    dnl libc headers tend to trail kernel support
+    dnl so compensate if necessary
+    AC_MSG_CHECKING([for timerfd cancel-on-set support])
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+    #include <sys/timerfd.h>
+    #include <unistd.h>
+    ],[
+    int
+    main (void)
+    {
+      struct itimerspec timer_spec = { 0 };
+      timerfd_settime (timerfd_create (CLOCK_MONOTONIC, TFD_CLOEXEC),
+                       TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+                       &timer_spec,
+                       NULL);
+
+      return 0;
+    }
+    ])],
+    [have_tfd_timer_cancel_on_set=yes],
+    [have_tfd_timer_cancel_on_set=no])
+    AC_MSG_RESULT($have_tfd_timer_cancel_on_set)
+    if test x"$have_tfd_timer_cancel_on_set" = x"no"; then
+       AC_DEFINE(TFD_TIMER_CANCEL_ON_SET, [(1 << 1)], [have timerfd support])
+    fi
+fi
+
 dnl ==============================================
 dnl libsocialweb
 dnl ==============================================
diff --git a/panels/user-accounts/Makefile.am b/panels/user-accounts/Makefile.am
index ce52837..cad8b60 100644
--- a/panels/user-accounts/Makefile.am
+++ b/panels/user-accounts/Makefile.am
@@ -25,6 +25,8 @@ endif
 libuser_accounts_la_SOURCES =		\
 	um-account-type.h		\
 	um-account-type.c 		\
+	um-alarm.h			\
+	um-alarm.c			\
 	um-identity-manager.h		\
 	um-identity-manager.c		\
 	um-identity.h			\
diff --git a/panels/user-accounts/um-alarm.c b/panels/user-accounts/um-alarm.c
new file mode 100644
index 0000000..29d82e5
--- /dev/null
+++ b/panels/user-accounts/um-alarm.c
@@ -0,0 +1,490 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * 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, 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Ray Strode
+ * Based on work by Colin Walters
+ */
+
+#include "config.h"
+
+#include "um-alarm.h"
+
+#ifdef HAVE_TIMERFD
+#include <sys/timerfd.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+
+typedef struct {
+        GSource      *source;
+        GInputStream *stream;
+} Timer;
+
+typedef struct {
+        GSource *source;
+} Timeout;
+
+#define MAX_TIMEOUT_INTERVAL (10 * 1000)
+
+typedef enum {
+    UM_ALARM_TYPE_UNSCHEDULED,
+    UM_ALARM_TYPE_TIMER,
+    UM_ALARM_TYPE_TIMEOUT,
+} UmAlarmType;
+
+struct _UmAlarmPrivate
+{
+        GCancellable *cancellable;
+        GDateTime    *time;
+        GDateTime    *previous_wakeup_time;
+        GMainContext *context;
+        GSource      *immediate_wakeup_source;
+
+        UmAlarmType type;
+        union {
+                Timer   timer;
+                Timeout timeout;
+        };
+};
+
+enum {
+        FIRED,
+        REARMED,
+        NUMBER_OF_SIGNALS,
+};
+
+static void schedule_wakeups (UmAlarm *self);
+static void schedule_wakeups_with_timeout_source (UmAlarm *self);
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (UmAlarm, um_alarm, G_TYPE_OBJECT);
+
+static void
+clear_scheduled_immediate_wakeup (UmAlarm *self)
+{
+        if (self->priv->immediate_wakeup_source != NULL) {
+                g_source_destroy (self->priv->immediate_wakeup_source);
+                self->priv->immediate_wakeup_source = NULL;
+        }
+}
+
+static void
+clear_scheduled_timer_wakeups (UmAlarm *self)
+{
+#ifdef HAVE_TIMERFD
+        GError *error;
+        gboolean is_closed;
+
+        if (self->priv->timer.stream == NULL) {
+                return;
+        }
+
+        g_source_destroy (self->priv->timer.source);
+        self->priv->timer.source = NULL;
+
+        error = NULL;
+        is_closed = g_input_stream_close (self->priv->timer.stream,
+                                          NULL,
+                                          &error);
+
+        if (!is_closed) {
+                g_warning ("UmAlarm: could not close timer stream: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (self->priv->timer.stream);
+        self->priv->timer.stream = NULL;
+
+#endif
+}
+
+static void
+clear_scheduled_timeout_wakeups (UmAlarm *self)
+{
+        g_source_destroy (self->priv->timeout.source);
+        self->priv->timeout.source = NULL;
+}
+
+static void
+clear_scheduled_wakeups (UmAlarm *self)
+{
+        clear_scheduled_immediate_wakeup (self);
+
+        switch (self->priv->type) {
+                case UM_ALARM_TYPE_TIMER:
+                        clear_scheduled_timer_wakeups (self);
+                        break;
+
+                case UM_ALARM_TYPE_TIMEOUT:
+                        clear_scheduled_timeout_wakeups (self);
+                        break;
+
+                default:
+                        break;
+        }
+
+        if (self->priv->cancellable != NULL) {
+                g_object_unref (self->priv->cancellable);
+                self->priv->cancellable = NULL;
+        }
+
+        if (self->priv->context != NULL) {
+                g_main_context_unref (self->priv->context);
+                self->priv->context = NULL;
+        }
+
+        if (self->priv->previous_wakeup_time != NULL) {
+                g_date_time_unref (self->priv->previous_wakeup_time);
+                self->priv->previous_wakeup_time = NULL;
+        }
+
+        self->priv->type = UM_ALARM_TYPE_UNSCHEDULED;
+}
+
+static void
+um_alarm_finalize (GObject *object)
+{
+        UmAlarm *self = UM_ALARM (object);
+
+        if (self->priv->cancellable != NULL &&
+            !g_cancellable_is_cancelled (self->priv->cancellable)) {
+                g_cancellable_cancel (self->priv->cancellable);
+        }
+
+        clear_scheduled_wakeups (self);
+
+        if (self->priv->time != NULL) {
+                g_date_time_unref (self->priv->time);
+        }
+
+        if (self->priv->previous_wakeup_time != NULL) {
+                g_date_time_unref (self->priv->previous_wakeup_time);
+        }
+
+        G_OBJECT_CLASS (um_alarm_parent_class)->finalize (object);
+}
+
+static void
+um_alarm_class_init (UmAlarmClass *klass)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = um_alarm_finalize;
+
+        g_type_class_add_private (klass, sizeof (UmAlarmPrivate));
+
+        signals[FIRED] = g_signal_new ("fired",
+                                       G_TYPE_FROM_CLASS (klass),
+                                       G_SIGNAL_RUN_LAST,
+                                       0,
+                                       NULL, NULL, NULL,
+                                       G_TYPE_NONE, 0);
+
+        signals[REARMED] = g_signal_new ("rearmed",
+                                         G_TYPE_FROM_CLASS (klass),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL, NULL, NULL,
+                                         G_TYPE_NONE, 0);
+}
+
+static void
+um_alarm_init (UmAlarm *self)
+{
+        self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                                  UM_TYPE_ALARM,
+                                                  UmAlarmPrivate);
+        self->priv->type = UM_ALARM_TYPE_UNSCHEDULED;
+}
+
+static void
+on_cancelled (GCancellable *cancellable,
+              gpointer      user_data)
+{
+        UmAlarm *self = UM_ALARM (user_data);
+
+        clear_scheduled_wakeups (self);
+}
+
+static void
+fire_alarm (UmAlarm *self)
+{
+        g_signal_emit (G_OBJECT (self), signals[FIRED], 0);
+}
+
+static void
+rearm_alarm (UmAlarm *self)
+{
+        g_signal_emit (G_OBJECT (self), signals[REARMED], 0);
+}
+
+static void
+fire_or_rearm_alarm (UmAlarm *self)
+{
+        GTimeSpan  time_until_fire;
+        GTimeSpan  previous_time_until_fire;
+        GDateTime *now;
+
+        now = g_date_time_new_now_local ();
+        time_until_fire = g_date_time_difference (self->priv->time, now);
+
+        if (self->priv->previous_wakeup_time == NULL) {
+                self->priv->previous_wakeup_time = now;
+
+                /* If, according to the time, we're past when we should have fired,
+                 * then fire the alarm.
+                 */
+                if (time_until_fire <= 0) {
+                        fire_alarm (self);
+                }
+        } else {
+                previous_time_until_fire = g_date_time_difference (self->priv->time,
+                                                                   self->priv->previous_wakeup_time);
+
+                g_date_time_unref (self->priv->previous_wakeup_time);
+                self->priv->previous_wakeup_time = now;
+
+                /* If, according to the time, we're past when we should have fired,
+                 * and this is the first wakeup where that's been true then fire
+                 * the alarm. The first check makes sure we don't fire prematurely,
+                 * and the second check makes sure we don't fire more than once
+                 */
+                if (time_until_fire <= 0 && previous_time_until_fire > 0) {
+                        fire_alarm (self);
+
+                /* If, according to the time, we're before when we should fire,
+                 * and we previously fired the alarm, then we've jumped back in
+                 * time and need to rearm the alarm.
+                 */
+                } else if (time_until_fire > 0 && previous_time_until_fire <= 0) {
+                        rearm_alarm (self);
+                }
+        }
+}
+
+static gboolean
+on_immediate_wakeup_source_ready (gpointer user_data)
+{
+        UmAlarm *self = UM_ALARM (user_data);
+
+        fire_or_rearm_alarm (self);
+
+        return FALSE;
+}
+
+#ifdef HAVE_TIMERFD
+static gboolean
+on_timer_source_ready (GObject  *stream,
+                       gpointer  user_data)
+{
+        UmAlarm *self = UM_ALARM (user_data);
+        gint64 number_of_fires;
+        gssize bytes_read;
+
+        bytes_read = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
+                                                               &number_of_fires,
+                                                               sizeof (gint64),
+                                                               NULL,
+                                                               NULL);
+
+        if (bytes_read == sizeof (gint64)) {
+                if (number_of_fires < 0 || number_of_fires > 1) {
+                        g_warning ("UmAlarm: expected timerfd to report firing once,"
+                                   "but it reported firing %ld times\n",
+                                   (long) number_of_fires);
+                }
+        }
+
+        fire_or_rearm_alarm (self);
+        return TRUE;
+}
+#endif
+
+static gboolean
+schedule_wakeups_with_timerfd (UmAlarm *self)
+{
+#ifdef HAVE_TIMERFD
+        struct itimerspec timer_spec;
+        int fd;
+        int result;
+
+        g_debug ("UmAlarm: trying to use kernel timer");
+
+        fd = timerfd_create (CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK);
+
+        if (fd < 0) {
+                g_debug ("UmAlarm: could not create timer fd: %m");
+                return FALSE;
+        }
+
+        memset (&timer_spec, 0, sizeof (timer_spec));
+        timer_spec.it_value.tv_sec = g_date_time_to_unix (self->priv->time) + 1;
+
+        result = timerfd_settime (fd,
+                                  TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+                                  &timer_spec,
+                                  NULL);
+
+        if (result < 0) {
+                g_debug ("UmAlarm: could not set timer: %m");
+                return FALSE;
+        }
+
+        self->priv->type = UM_ALARM_TYPE_TIMER;
+        self->priv->timer.stream = g_unix_input_stream_new (fd, TRUE);
+
+        self->priv->timer.source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (self->priv->timer.stream),
+                                                                          self->priv->cancellable);
+        g_source_set_callback (self->priv->timer.source,
+                               (GSourceFunc)
+                               on_timer_source_ready,
+                               self,
+                               NULL);
+        g_source_attach (self->priv->timer.source,
+                         self->priv->context);
+
+        return TRUE;
+
+#endif /* HAVE_TIMERFD */
+
+    return FALSE;
+}
+
+static gboolean
+on_timeout_source_ready (gpointer user_data)
+{
+        UmAlarm *self = UM_ALARM (user_data);
+
+        fire_or_rearm_alarm (self);
+
+        schedule_wakeups_with_timeout_source (self);
+
+        return FALSE;
+}
+
+static void
+schedule_wakeups_with_timeout_source (UmAlarm *self)
+{
+        GDateTime *now;
+        GTimeSpan time_span;
+        guint interval;
+        self->priv->type = UM_ALARM_TYPE_TIMEOUT;
+
+        now = g_date_time_new_now_local ();
+        time_span = g_date_time_difference (self->priv->time, now);
+        g_date_time_unref (now);
+
+        time_span = CLAMP (time_span, 1000 * G_TIME_SPAN_MILLISECOND, G_MAXUINT * G_TIME_SPAN_MILLISECOND);
+        interval = time_span / G_TIME_SPAN_MILLISECOND;
+
+        /* We poll every 10 seconds or so because we want to catch time skew
+         */
+        interval = MIN (interval, MAX_TIMEOUT_INTERVAL);
+
+        self->priv->timeout.source = g_timeout_source_new (interval);
+        g_source_set_callback (self->priv->timeout.source,
+                               (GSourceFunc)
+                               on_timeout_source_ready,
+                               self,
+                               NULL);
+
+        g_source_attach (self->priv->timeout.source,
+                         self->priv->context);
+}
+
+static void
+schedule_wakeups (UmAlarm *self)
+{
+        gboolean wakeup_scheduled;
+
+        wakeup_scheduled = schedule_wakeups_with_timerfd (self);
+
+        if (!wakeup_scheduled) {
+                g_debug ("UmAlarm: falling back to polling timeout\n");
+                schedule_wakeups_with_timeout_source (self);
+        }
+}
+
+static void
+schedule_immediate_wakeup (UmAlarm *self)
+{
+        self->priv->immediate_wakeup_source = g_idle_source_new ();
+
+        g_source_set_callback (self->priv->immediate_wakeup_source,
+                               (GSourceFunc)
+                               on_immediate_wakeup_source_ready,
+                               self,
+                               NULL);
+
+        g_source_attach (self->priv->immediate_wakeup_source,
+                         self->priv->context);
+}
+
+void
+um_alarm_set (UmAlarm      *self,
+              GDateTime    *time,
+              GCancellable *cancellable)
+{
+        if (self->priv->cancellable != NULL) {
+                if (!g_cancellable_is_cancelled (self->priv->cancellable)) {
+                        g_cancellable_cancel (cancellable);
+                }
+
+                if (self->priv->cancellable != NULL) {
+                    g_object_unref (self->priv->cancellable);
+                    self->priv->cancellable = NULL;
+                }
+        }
+
+        if (cancellable == NULL) {
+                self->priv->cancellable = g_cancellable_new ();
+        } else {
+                self->priv->cancellable = g_object_ref (cancellable);
+        }
+
+        g_cancellable_connect (self->priv->cancellable,
+                               G_CALLBACK (on_cancelled),
+                               self,
+                               NULL);
+        self->priv->time = g_date_time_ref (time);
+        self->priv->context = g_main_context_ref (g_main_context_default ());
+
+        schedule_wakeups (self);
+
+        /* Wake up right away, in case it's already expired leaving the gate */
+        schedule_immediate_wakeup (self);
+}
+
+UmAlarm *
+um_alarm_new (void)
+{
+        UmAlarm *self;
+
+        self = UM_ALARM (g_object_new (UM_TYPE_ALARM, NULL));
+
+        return UM_ALARM (self);
+}
diff --git a/panels/user-accounts/um-alarm.h b/panels/user-accounts/um-alarm.h
new file mode 100644
index 0000000..6eeb8d7
--- /dev/null
+++ b/panels/user-accounts/um-alarm.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors: Ray Strode
+ */
+
+#ifndef __UM_ALARM_H__
+#define __UM_ALARM_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_ALARM             (um_alarm_get_type ())
+#define UM_ALARM(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_ALARM, UmAlarm))
+#define UM_ALARM_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_ALARM, UmAlarmClass))
+#define UM_IS_ALARM(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_ALARM))
+#define UM_IS_ALARM_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_ALARM))
+#define UM_ALARM_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UM_TYPE_ALARM, UmAlarmClass))
+
+typedef struct _UmAlarm        UmAlarm;
+typedef struct _UmAlarmClass   UmAlarmClass;
+typedef struct _UmAlarmPrivate UmAlarmPrivate;
+
+struct _UmAlarm
+{
+        GObject parent;
+
+        UmAlarmPrivate *priv;
+};
+
+struct _UmAlarmClass
+{
+        GObjectClass parent_class;
+
+        void     (* fired)       (UmAlarm *alarm);
+        void     (* rearmed)     (UmAlarm *alarm);
+};
+
+GType         um_alarm_get_type (void);
+
+UmAlarm      *um_alarm_new    (void);
+void          um_alarm_set    (UmAlarm      *alarm,
+                               GDateTime    *time,
+                               GCancellable *cancellable);
+G_END_DECLS
+
+#endif /* __UM_ALARM_H__ */



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