summaryrefslogtreecommitdiffstats
path: root/src/ck-file-monitor-inotify.c
diff options
context:
space:
mode:
authorWilliam Jon McCann <mccann@jhu.edu>2007-04-05 15:21:14 -0400
committerWilliam Jon McCann <mccann@jhu.edu>2007-04-05 15:21:14 -0400
commite0244a8f6dd0b7f8ebecc6bec52c013ce5286279 (patch)
tree9e890e4d7bba3ed70194c012fccbd159357afa1f /src/ck-file-monitor-inotify.c
parent33dcd02c399e3255a7a64c1e90b258d79c14f2c4 (diff)
use inotify to detect activity on tty when possible
Diffstat (limited to 'src/ck-file-monitor-inotify.c')
-rw-r--r--src/ck-file-monitor-inotify.c669
1 files changed, 669 insertions, 0 deletions
diff --git a/src/ck-file-monitor-inotify.c b/src/ck-file-monitor-inotify.c
new file mode 100644
index 0000000..6e775e2
--- /dev/null
+++ b/src/ck-file-monitor-inotify.c
@@ -0,0 +1,669 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/inotify.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include "ck-file-monitor.h"
+
+#define CK_FILE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CK_TYPE_FILE_MONITOR, CkFileMonitorPrivate))
+
+typedef struct
+{
+ int wd;
+ char *path;
+ GSList *notifies;
+} FileInotifyWatch;
+
+typedef struct
+{
+ guint id;
+ int mask;
+ CkFileMonitorNotifyFunc notify_func;
+ gpointer user_data;
+ FileInotifyWatch *watch;
+} FileMonitorNotify;
+
+typedef struct
+{
+ FileInotifyWatch *watch;
+ CkFileMonitorEvent event;
+ char *path;
+} FileMonitorEventInfo;
+
+#define DEFAULT_NOTIFY_BUFLEN (32 * (sizeof (struct inotify_event) + 16))
+#define MAX_NOTIFY_BUFLEN (32 * DEFAULT_NOTIFY_BUFLEN)
+
+struct CkFileMonitorPrivate
+{
+ guint serial;
+
+ gboolean initialized_inotify;
+
+ int inotify_fd;
+ guint io_watch;
+
+ GHashTable *wd_to_watch;
+ GHashTable *path_to_watch;
+ GHashTable *notifies;
+
+ guint buflen;
+ guchar *buffer;
+
+ guint events_idle_id;
+ GQueue *notify_events;
+};
+
+enum {
+ PROP_0,
+};
+
+static void ck_file_monitor_class_init (CkFileMonitorClass *klass);
+static void ck_file_monitor_init (CkFileMonitor *file_monitor);
+static void ck_file_monitor_finalize (GObject *object);
+
+G_DEFINE_TYPE (CkFileMonitor, ck_file_monitor, G_TYPE_OBJECT)
+
+static gpointer monitor_object = NULL;
+
+GQuark
+ck_file_monitor_error_quark (void)
+{
+ static GQuark ret = 0;
+ if (ret == 0) {
+ ret = g_quark_from_static_string ("ck_file_monitor_error");
+ }
+
+ return ret;
+}
+
+/* most of this is adapted from libgnome-menu */
+
+static int
+our_event_mask_to_inotify_mask (int our_mask)
+{
+ int mask;
+
+ mask = 0;
+
+ if (our_mask & CK_FILE_MONITOR_EVENT_ACCESS) {
+ mask |= IN_ACCESS;
+ }
+
+ if (our_mask & CK_FILE_MONITOR_EVENT_CREATE) {
+ mask |= IN_CREATE | IN_MOVED_TO;
+ }
+
+ if (our_mask & CK_FILE_MONITOR_EVENT_DELETE) {
+ mask |= IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF;
+ }
+
+ if (our_mask & CK_FILE_MONITOR_EVENT_CHANGE) {
+ mask |= IN_MODIFY | IN_ATTRIB;
+ }
+
+ return mask;
+}
+
+static char *
+imask_to_string (guint32 mask)
+{
+ GString *out;
+
+ out = g_string_new (NULL);
+
+ if (mask & IN_ACCESS) {
+ g_string_append (out, "ACCESS ");
+ }
+ if (mask & IN_MODIFY) {
+ g_string_append (out, "MODIFY ");
+ }
+ if (mask & IN_ATTRIB) {
+ g_string_append (out, "ATTRIB ");
+ }
+ if (mask & IN_CLOSE_WRITE) {
+ g_string_append (out, "CLOSE_WRITE ");
+ }
+ if (mask & IN_CLOSE_NOWRITE) {
+ g_string_append (out, "CLOSE_NOWRITE ");
+ }
+ if (mask & IN_OPEN) {
+ g_string_append (out, "OPEN ");
+ }
+ if (mask & IN_MOVED_FROM) {
+ g_string_append (out, "MOVED_FROM ");
+ }
+ if (mask & IN_MOVED_TO) {
+ g_string_append (out, "MOVED_TO ");
+ }
+ if (mask & IN_DELETE) {
+ g_string_append (out, "DELETE ");
+ }
+ if (mask & IN_CREATE) {
+ g_string_append (out, "CREATE ");
+ }
+ if (mask & IN_DELETE_SELF) {
+ g_string_append (out, "DELETE_SELF ");
+ }
+ if (mask & IN_UNMOUNT) {
+ g_string_append (out, "UNMOUNT ");
+ }
+ if (mask & IN_Q_OVERFLOW) {
+ g_string_append (out, "Q_OVERFLOW ");
+ }
+ if (mask & IN_IGNORED) {
+ g_string_append (out, "IGNORED ");
+ }
+
+ return g_string_free (out, FALSE);
+}
+
+static FileInotifyWatch *
+file_monitor_add_watch_for_path (CkFileMonitor *monitor,
+ const char *path,
+ int mask)
+
+{
+ FileInotifyWatch *watch;
+ int wd;
+ int imask;
+ char *mask_str;
+
+ imask = our_event_mask_to_inotify_mask (mask);
+
+ mask_str = imask_to_string (imask);
+ g_debug ("adding inotify watch %s", mask_str);
+ g_free (mask_str);
+
+ wd = inotify_add_watch (monitor->priv->inotify_fd, path, IN_MASK_ADD | imask);
+ if (wd < 0) {
+ /* FIXME: remove watch etc */
+ return NULL;
+ }
+
+ watch = g_hash_table_lookup (monitor->priv->path_to_watch, path);
+ if (watch == NULL) {
+ watch = g_new0 (FileInotifyWatch, 1);
+
+ watch->wd = wd;
+ watch->path = g_strdup (path);
+
+ g_hash_table_insert (monitor->priv->path_to_watch, watch->path, watch);
+ g_hash_table_insert (monitor->priv->wd_to_watch, GINT_TO_POINTER (wd), watch);
+ }
+
+ return watch;
+}
+
+static void
+monitor_release_watch (CkFileMonitor *monitor,
+ FileInotifyWatch *watch)
+{
+ g_slist_free (watch->notifies);
+ watch->notifies = NULL;
+
+ g_free (watch->path);
+ watch->path = NULL;
+
+ inotify_rm_watch (monitor->priv->inotify_fd, watch->wd);
+ watch->wd = -1;
+}
+
+static void
+file_monitor_remove_watch (CkFileMonitor *monitor,
+ FileInotifyWatch *watch)
+{
+ g_hash_table_remove (monitor->priv->path_to_watch,
+ watch->path);
+ g_hash_table_remove (monitor->priv->wd_to_watch,
+ GINT_TO_POINTER (watch->wd));
+ monitor_release_watch (monitor, watch);
+}
+
+static gboolean
+remove_watch_foreach (const char *path,
+ FileInotifyWatch *watch,
+ CkFileMonitor *monitor)
+{
+ monitor_release_watch (monitor, watch);
+ return TRUE;
+}
+
+static void
+close_inotify (CkFileMonitor *monitor)
+{
+ if (! monitor->priv->initialized_inotify) {
+ return;
+ }
+
+ monitor->priv->initialized_inotify = FALSE;
+
+ g_hash_table_foreach_remove (monitor->priv->path_to_watch,
+ (GHRFunc) remove_watch_foreach,
+ monitor);
+ monitor->priv->path_to_watch = NULL;
+
+ if (monitor->priv->wd_to_watch != NULL) {
+ g_hash_table_destroy (monitor->priv->wd_to_watch);
+ }
+ monitor->priv->wd_to_watch = NULL;
+
+ g_free (monitor->priv->buffer);
+ monitor->priv->buffer = NULL;
+ monitor->priv->buflen = 0;
+
+ if (monitor->priv->io_watch) {
+ g_source_remove (monitor->priv->io_watch);
+ }
+ monitor->priv->io_watch = 0;
+
+ if (monitor->priv->inotify_fd > 0) {
+ close (monitor->priv->inotify_fd);
+ }
+ monitor->priv->inotify_fd = 0;
+}
+
+static gboolean
+emit_events_in_idle (CkFileMonitor *monitor)
+{
+ FileMonitorEventInfo *event_info;
+
+ monitor->priv->events_idle_id = 0;
+
+ while ((event_info = g_queue_pop_head (monitor->priv->notify_events)) != NULL) {
+ GSList *l;
+ FileInotifyWatch *watch;
+
+ watch = event_info->watch;
+
+ for (l = watch->notifies; l != NULL; l = l->next) {
+ FileMonitorNotify *notify;
+
+ notify = g_hash_table_lookup (monitor->priv->notifies,
+ GUINT_TO_POINTER (l->data));
+ if (notify == NULL) {
+ continue;
+ }
+
+ if (! (notify->mask & event_info->event)) {
+ continue;
+ }
+
+ if (notify->notify_func) {
+ notify->notify_func (monitor, event_info->event, event_info->path, notify->user_data);
+ }
+ }
+
+ g_free (event_info->path);
+ event_info->path = NULL;
+
+ event_info->event = CK_FILE_MONITOR_EVENT_NONE;
+
+ g_free (event_info);
+ }
+
+ return FALSE;
+}
+
+static void
+file_monitor_queue_event (CkFileMonitor *monitor,
+ FileMonitorEventInfo *event_info)
+{
+ g_queue_push_tail (monitor->priv->notify_events, event_info);
+
+ if (monitor->priv->events_idle_id == 0) {
+ monitor->priv->events_idle_id = g_idle_add ((GSourceFunc) emit_events_in_idle, monitor);
+ }
+}
+
+static void
+queue_watch_event (CkFileMonitor *monitor,
+ FileInotifyWatch *watch,
+ CkFileMonitorEvent event,
+ const char *path)
+{
+ FileMonitorEventInfo *event_info;
+
+ event_info = g_new0 (FileMonitorEventInfo, 1);
+
+ event_info->watch = watch;
+ event_info->path = g_strdup (path);
+ event_info->event = event;
+
+ file_monitor_queue_event (monitor, event_info);
+}
+
+static void
+handle_inotify_event (CkFileMonitor *monitor,
+ FileInotifyWatch *watch,
+ struct inotify_event *ievent)
+{
+ CkFileMonitorEvent event;
+ const char *path;
+ char *freeme;
+ char *mask_str;
+
+ freeme = NULL;
+
+ if (ievent->len > 0) {
+ path = freeme = g_build_filename (watch->path, ievent->name, NULL);
+ } else {
+ path = watch->path;
+ }
+
+ mask_str = imask_to_string (ievent->mask);
+ g_debug ("handing inotify event %s for %s", mask_str, path);
+ g_free (mask_str);
+
+ event = CK_FILE_MONITOR_EVENT_NONE;
+
+ if (ievent->mask & (IN_CREATE | IN_MOVED_TO)) {
+ event = CK_FILE_MONITOR_EVENT_CREATE;
+ } else if (ievent->mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF)) {
+ event = CK_FILE_MONITOR_EVENT_DELETE;
+ } else if (ievent->mask & (IN_MODIFY | IN_ATTRIB)) {
+ event = CK_FILE_MONITOR_EVENT_CHANGE;
+ } else if (ievent->mask & IN_ACCESS) {
+ event = CK_FILE_MONITOR_EVENT_ACCESS;
+ }
+
+ if (event != CK_FILE_MONITOR_EVENT_NONE) {
+ queue_watch_event (monitor, watch, event, path);
+ }
+
+ if (ievent->mask & IN_IGNORED) {
+ file_monitor_remove_watch (monitor, watch);
+ }
+}
+
+static gboolean
+inotify_data_pending (GIOChannel *source,
+ GIOCondition condition,
+ CkFileMonitor *monitor)
+{
+ int len;
+ int i;
+
+ g_debug ("Inotify data pending");
+
+ g_assert (monitor->priv->inotify_fd > 0);
+ g_assert (monitor->priv->buffer != NULL);
+
+ do {
+ while ((len = read (monitor->priv->inotify_fd, monitor->priv->buffer, monitor->priv->buflen)) < 0 && errno == EINTR);
+
+ if (len > 0) {
+ break;
+ } else if (len < 0) {
+ g_warning ("Error reading inotify event: %s",
+ g_strerror (errno));
+ goto error_cancel;
+ }
+
+ g_assert (len == 0);
+
+ if ((monitor->priv->buflen << 1) > MAX_NOTIFY_BUFLEN) {
+ g_warning ("Error reading inotify event: Exceded maximum buffer size");
+ goto error_cancel;
+ }
+
+ g_debug ("Buffer size %u too small, trying again at %u\n",
+ monitor->priv->buflen, monitor->priv->buflen << 1);
+
+ monitor->priv->buflen <<= 1;
+ monitor->priv->buffer = g_realloc (monitor->priv->buffer, monitor->priv->buflen);
+ } while (TRUE);
+
+ g_debug ("Inotify buffer filled");
+
+ i = 0;
+ while (i < len) {
+ struct inotify_event *ievent = (struct inotify_event *) &monitor->priv->buffer [i];
+ FileInotifyWatch *watch;
+
+ g_debug ("Got event wd = %d, mask = 0x%x, cookie = %d, len = %d, name= %s\n",
+ ievent->wd,
+ ievent->mask,
+ ievent->cookie,
+ ievent->len,
+ ievent->len > 0 ? ievent->name : "<none>");
+
+ watch = g_hash_table_lookup (monitor->priv->wd_to_watch,
+ GINT_TO_POINTER (ievent->wd));
+ if (watch != NULL) {
+ handle_inotify_event (monitor, watch, ievent);
+ }
+
+ i += sizeof (struct inotify_event) + ievent->len;
+ }
+
+ return TRUE;
+
+ error_cancel:
+ monitor->priv->io_watch = 0;
+
+ close_inotify (monitor);
+
+ return FALSE;
+}
+
+static FileMonitorNotify *
+file_monitor_add_notify_for_path (CkFileMonitor *monitor,
+ const char *path,
+ int mask,
+ CkFileMonitorNotifyFunc notify_func,
+ gpointer data)
+{
+ FileMonitorNotify *notify;
+ FileInotifyWatch *watch;
+
+ notify = NULL;
+
+ watch = file_monitor_add_watch_for_path (monitor, path, mask);
+ if (watch != NULL) {
+ notify = g_new0 (FileMonitorNotify, 1);
+ notify->notify_func = notify_func;
+ notify->user_data = data;
+ notify->id = monitor->priv->serial++;
+ notify->watch = watch;
+ notify->mask = mask;
+
+ g_debug ("Adding notify for %s mask:%d", path, mask);
+
+ g_hash_table_insert (monitor->priv->notifies, GUINT_TO_POINTER (notify->id), notify);
+ watch->notifies = g_slist_prepend (watch->notifies, GUINT_TO_POINTER (notify->id));
+ }
+
+ return notify;
+}
+
+static void
+file_monitor_remove_notify (CkFileMonitor *monitor,
+ guint id)
+{
+ FileMonitorNotify *notify;
+
+ g_debug ("removing notify for %u", id);
+
+ notify = g_hash_table_lookup (monitor->priv->notifies,
+ GUINT_TO_POINTER (id));
+ if (notify == NULL) {
+ return;
+ }
+
+ g_hash_table_steal (monitor->priv->notifies,
+ GUINT_TO_POINTER (id));
+
+ notify->watch->notifies = g_slist_remove (notify->watch->notifies, GUINT_TO_POINTER (id));
+
+ if (g_slist_length (notify->watch->notifies) == 0) {
+ file_monitor_remove_watch (monitor, notify->watch);
+ g_free (notify->watch);
+ }
+
+ g_free (notify);
+}
+
+guint
+ck_file_monitor_add_notify (CkFileMonitor *monitor,
+ const char *path,
+ int mask,
+ CkFileMonitorNotifyFunc notify_func,
+ gpointer data)
+{
+ FileMonitorNotify *notify;
+
+ if (! monitor->priv->initialized_inotify) {
+ return 0;
+ }
+
+ notify = file_monitor_add_notify_for_path (monitor,
+ path,
+ mask,
+ notify_func,
+ data);
+ if (notify == NULL) {
+ g_warning ("Failed to add monitor on '%s': %s",
+ path,
+ g_strerror (errno));
+ return 0;
+ }
+
+ return notify->id;
+}
+
+void
+ck_file_monitor_remove_notify (CkFileMonitor *monitor,
+ guint id)
+{
+ if (! monitor->priv->initialized_inotify) {
+ return;
+ }
+
+ file_monitor_remove_notify (monitor, id);
+}
+
+static void
+ck_file_monitor_class_init (CkFileMonitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ck_file_monitor_finalize;
+
+ g_type_class_add_private (klass, sizeof (CkFileMonitorPrivate));
+}
+
+
+static void
+setup_inotify (CkFileMonitor *monitor)
+{
+ GIOChannel *io_channel;
+ int fd;
+
+ if (monitor->priv->initialized_inotify) {
+ return;
+ }
+
+ if ((fd = inotify_init ()) < 0) {
+ g_warning ("Failed to initialize inotify: %s",
+ g_strerror (errno));
+ return;
+ }
+
+ monitor->priv->inotify_fd = fd;
+
+ io_channel = g_io_channel_unix_new (fd);
+ monitor->priv->io_watch = g_io_add_watch (io_channel,
+ G_IO_IN|G_IO_PRI,
+ (GIOFunc) inotify_data_pending,
+ monitor);
+ g_io_channel_unref (io_channel);
+
+ monitor->priv->buflen = DEFAULT_NOTIFY_BUFLEN;
+ monitor->priv->buffer = g_malloc (DEFAULT_NOTIFY_BUFLEN);
+
+ monitor->priv->notifies = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ monitor->priv->wd_to_watch = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+ monitor->priv->path_to_watch = g_hash_table_new (g_str_hash,
+ g_str_equal);
+
+ monitor->priv->initialized_inotify = TRUE;
+}
+
+static void
+ck_file_monitor_init (CkFileMonitor *monitor)
+{
+ monitor->priv = CK_FILE_MONITOR_GET_PRIVATE (monitor);
+
+ monitor->priv->serial = 1;
+ monitor->priv->notify_events = g_queue_new ();
+
+ setup_inotify (monitor);
+}
+
+static void
+ck_file_monitor_finalize (GObject *object)
+{
+ CkFileMonitor *monitor;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (CK_IS_FILE_MONITOR (object));
+
+ monitor = CK_FILE_MONITOR (object);
+
+ g_return_if_fail (monitor->priv != NULL);
+
+ close_inotify (monitor);
+
+ g_hash_table_destroy (monitor->priv->notifies);
+ g_queue_free (monitor->priv->notify_events);
+
+ G_OBJECT_CLASS (ck_file_monitor_parent_class)->finalize (object);
+}
+
+CkFileMonitor *
+ck_file_monitor_new (void)
+{
+ if (monitor_object != NULL) {
+ g_object_ref (monitor_object);
+ } else {
+ monitor_object = g_object_new (CK_TYPE_FILE_MONITOR, NULL);
+
+ g_object_add_weak_pointer (monitor_object,
+ (gpointer *) &monitor_object);
+ }
+
+ return CK_FILE_MONITOR (monitor_object);
+}