Blame gio/inotify/inotify-helper.c

Packit ae235b
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
Packit ae235b
Packit ae235b
/* inotify-helper.c - GVFS Monitor based on inotify.
Packit ae235b
Packit ae235b
   Copyright (C) 2007 John McCutchan
Packit ae235b
Packit ae235b
   This library is free software; you can redistribute it and/or
Packit ae235b
   modify it under the terms of the GNU Lesser General Public
Packit ae235b
   License as published by the Free Software Foundation; either
Packit ae235b
   version 2.1 of the License, or (at your option) any later version.
Packit ae235b
Packit ae235b
   This library is distributed in the hope that it will be useful,
Packit ae235b
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
   Lesser General Public License for more details.
Packit ae235b
Packit ae235b
   You should have received a copy of the GNU Lesser General Public License
Packit ae235b
   along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
Packit ae235b
   Authors: 
Packit ae235b
		 John McCutchan <john@johnmccutchan.com>
Packit ae235b
*/
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
#include <errno.h>
Packit ae235b
#include <time.h>
Packit ae235b
#include <string.h>
Packit ae235b
#include <sys/ioctl.h>
Packit ae235b
#include <sys/stat.h>
Packit ae235b
/* Just include the local header to stop all the pain */
Packit ae235b
#include <sys/inotify.h>
Packit ae235b
#include <gio/glocalfilemonitor.h>
Packit ae235b
#include <gio/gfile.h>
Packit ae235b
#include "inotify-helper.h"
Packit ae235b
#include "inotify-missing.h"
Packit ae235b
#include "inotify-path.h"
Packit ae235b
Packit ae235b
static gboolean ih_debug_enabled = FALSE;
Packit ae235b
#define IH_W if (ih_debug_enabled) g_warning 
Packit ae235b
Packit ae235b
static gboolean ih_event_callback (ik_event_t  *event,
Packit ae235b
                                   inotify_sub *sub,
Packit ae235b
                                   gboolean     file_event);
Packit ae235b
static void ih_not_missing_callback (inotify_sub *sub);
Packit ae235b
Packit ae235b
/* We share this lock with inotify-kernel.c and inotify-missing.c
Packit ae235b
 *
Packit ae235b
 * inotify-kernel.c takes the lock when it reads events from
Packit ae235b
 * the kernel and when it processes those events
Packit ae235b
 *
Packit ae235b
 * inotify-missing.c takes the lock when it is scanning the missing
Packit ae235b
 * list.
Packit ae235b
 *
Packit ae235b
 * We take the lock in all public functions
Packit ae235b
 */
Packit ae235b
G_LOCK_DEFINE (inotify_lock);
Packit ae235b
Packit ae235b
static GFileMonitorEvent ih_mask_to_EventFlags (guint32 mask);
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * _ih_startup:
Packit ae235b
 *
Packit ae235b
 * Initializes the inotify backend.  This must be called before
Packit ae235b
 * any other functions in this module.
Packit ae235b
 *
Packit ae235b
 * Returns: #TRUE if initialization succeeded, #FALSE otherwise
Packit ae235b
 */
Packit ae235b
gboolean
Packit ae235b
_ih_startup (void)
Packit ae235b
{
Packit ae235b
  static gboolean initialized = FALSE;
Packit ae235b
  static gboolean result = FALSE;
Packit ae235b
  
Packit ae235b
  G_LOCK (inotify_lock);
Packit ae235b
  
Packit ae235b
  if (initialized == TRUE)
Packit ae235b
    {
Packit ae235b
      G_UNLOCK (inotify_lock);
Packit ae235b
      return result;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  result = _ip_startup (ih_event_callback);
Packit ae235b
  if (!result)
Packit ae235b
    {
Packit ae235b
      G_UNLOCK (inotify_lock);
Packit ae235b
      return FALSE;
Packit ae235b
    }
Packit ae235b
  _im_startup (ih_not_missing_callback);
Packit ae235b
Packit ae235b
  IH_W ("started gvfs inotify backend\n");
Packit ae235b
  
Packit ae235b
  initialized = TRUE;
Packit ae235b
  
Packit ae235b
  G_UNLOCK (inotify_lock);
Packit ae235b
  
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
/*
Packit ae235b
 * Adds a subscription to be monitored.
Packit ae235b
 */
Packit ae235b
gboolean
Packit ae235b
_ih_sub_add (inotify_sub *sub)
Packit ae235b
{
Packit ae235b
  G_LOCK (inotify_lock);
Packit ae235b
	
Packit ae235b
  if (!_ip_start_watching (sub))
Packit ae235b
    _im_add (sub);
Packit ae235b
  
Packit ae235b
  G_UNLOCK (inotify_lock);
Packit ae235b
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
/*
Packit ae235b
 * Cancels a subscription which was being monitored.
Packit ae235b
 */
Packit ae235b
gboolean
Packit ae235b
_ih_sub_cancel (inotify_sub *sub)
Packit ae235b
{
Packit ae235b
  G_LOCK (inotify_lock);
Packit ae235b
Packit ae235b
  if (!sub->cancelled)
Packit ae235b
    {
Packit ae235b
      IH_W ("cancelling %s\n", sub->dirname);
Packit ae235b
      sub->cancelled = TRUE;
Packit ae235b
      _im_rm (sub);
Packit ae235b
      _ip_stop_watching (sub);
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  G_UNLOCK (inotify_lock);
Packit ae235b
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
static char *
Packit ae235b
_ih_fullpath_from_event (ik_event_t *event,
Packit ae235b
			 const char *dirname,
Packit ae235b
			 const char *filename)
Packit ae235b
{
Packit ae235b
  char *fullpath;
Packit ae235b
Packit ae235b
  if (filename)
Packit ae235b
    fullpath = g_strdup_printf ("%s/%s", dirname, filename);
Packit ae235b
  else if (event->name)
Packit ae235b
    fullpath = g_strdup_printf ("%s/%s", dirname, event->name);
Packit ae235b
  else
Packit ae235b
    fullpath = g_strdup_printf ("%s/", dirname);
Packit ae235b
Packit ae235b
   return fullpath;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
ih_event_callback (ik_event_t  *event,
Packit ae235b
                   inotify_sub *sub,
Packit ae235b
                   gboolean     file_event)
Packit ae235b
{
Packit ae235b
  gboolean interesting;
Packit ae235b
  GFileMonitorEvent event_flags;
Packit ae235b
Packit ae235b
  g_assert (!file_event); /* XXX hardlink support */
Packit ae235b
Packit ae235b
  event_flags = ih_mask_to_EventFlags (event->mask);
Packit ae235b
Packit ae235b
  if (event->mask & IN_MOVE)
Packit ae235b
    {
Packit ae235b
      /* We either have a rename (in the same directory) or a move
Packit ae235b
       * (between different directories).
Packit ae235b
       */
Packit ae235b
      if (event->pair && event->pair->wd == event->wd)
Packit ae235b
        {
Packit ae235b
          /* this is a rename */
Packit ae235b
          interesting = g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
Packit ae235b
                                                            event->name, event->pair->name, NULL, event->timestamp);
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          GFile *other;
Packit ae235b
Packit ae235b
          if (event->pair)
Packit ae235b
            {
Packit ae235b
              const char *parent_dir;
Packit ae235b
              gchar *fullpath;
Packit ae235b
Packit ae235b
              parent_dir = _ip_get_path_for_wd (event->pair->wd);
Packit ae235b
              fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
Packit ae235b
              other = g_file_new_for_path (fullpath);
Packit ae235b
              g_free (fullpath);
Packit ae235b
            }
Packit ae235b
          else
Packit ae235b
            other = NULL;
Packit ae235b
Packit ae235b
          /* This is either an incoming or outgoing move. Since we checked the
Packit ae235b
           * event->mask above, it should have converted to a #GFileMonitorEvent
Packit ae235b
           * properly. If not, the assumption we have made about event->mask
Packit ae235b
           * only ever having a single bit set (apart from IN_ISDIR) is false.
Packit ae235b
           * The kernel documentation is lacking here. */
Packit ae235b
          g_assert (event_flags != -1);
Packit ae235b
          interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
Packit ae235b
                                                            event->name, NULL, other, event->timestamp);
Packit ae235b
Packit ae235b
          if (other)
Packit ae235b
            g_object_unref (other);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
  else if (event_flags != -1)
Packit ae235b
    /* unpaired event -- no 'other' field */
Packit ae235b
    interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
Packit ae235b
                                                      event->name, NULL, NULL, event->timestamp);
Packit ae235b
Packit ae235b
  if (event->mask & IN_CREATE)
Packit ae235b
    {
Packit ae235b
      const gchar *parent_dir;
Packit ae235b
      gchar *fullname;
Packit ae235b
      struct stat buf;
Packit ae235b
      gint s;
Packit ae235b
Packit ae235b
      /* The kernel reports IN_CREATE for two types of events:
Packit ae235b
       *
Packit ae235b
       *  - creat(), in which case IN_CLOSE_WRITE will come soon; or
Packit ae235b
       *  - link(), mkdir(), mknod(), etc., in which case it won't
Packit ae235b
       *
Packit ae235b
       * We can attempt to detect the second case and send the
Packit ae235b
       * CHANGES_DONE immediately so that the user isn't left waiting.
Packit ae235b
       *
Packit ae235b
       * The detection for link() is not 100% reliable since the link
Packit ae235b
       * count could be 1 if the original link was deleted or if
Packit ae235b
       * O_TMPFILE was being used, but in that case the virtual
Packit ae235b
       * CHANGES_DONE will be emitted to close the loop.
Packit ae235b
       */
Packit ae235b
Packit ae235b
      parent_dir = _ip_get_path_for_wd (event->wd);
Packit ae235b
      fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
Packit ae235b
      s = stat (fullname, &buf;;
Packit ae235b
      g_free (fullname);
Packit ae235b
Packit ae235b
      /* if it doesn't look like the result of creat()... */
Packit ae235b
      if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
Packit ae235b
        g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
Packit ae235b
                                            event->name, NULL, NULL, event->timestamp);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return interesting;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
ih_not_missing_callback (inotify_sub *sub)
Packit ae235b
{
Packit ae235b
  gint now = g_get_monotonic_time ();
Packit ae235b
Packit ae235b
  g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
Packit ae235b
                                      sub->filename, NULL, NULL, now);
Packit ae235b
  g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
Packit ae235b
                                      sub->filename, NULL, NULL, now);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Transforms a inotify event to a GVFS event. */
Packit ae235b
static GFileMonitorEvent
Packit ae235b
ih_mask_to_EventFlags (guint32 mask)
Packit ae235b
{
Packit ae235b
  mask &= ~IN_ISDIR;
Packit ae235b
  switch (mask)
Packit ae235b
    {
Packit ae235b
    case IN_MODIFY:
Packit ae235b
      return G_FILE_MONITOR_EVENT_CHANGED;
Packit ae235b
    case IN_CLOSE_WRITE:
Packit ae235b
      return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT;
Packit ae235b
    case IN_ATTRIB:
Packit ae235b
      return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
Packit ae235b
    case IN_MOVE_SELF:
Packit ae235b
    case IN_DELETE:
Packit ae235b
    case IN_DELETE_SELF:
Packit ae235b
      return G_FILE_MONITOR_EVENT_DELETED;
Packit ae235b
    case IN_CREATE:
Packit ae235b
      return G_FILE_MONITOR_EVENT_CREATED;
Packit ae235b
    case IN_MOVED_FROM:
Packit ae235b
      return G_FILE_MONITOR_EVENT_MOVED_OUT;
Packit ae235b
    case IN_MOVED_TO:
Packit ae235b
      return G_FILE_MONITOR_EVENT_MOVED_IN;
Packit ae235b
    case IN_UNMOUNT:
Packit ae235b
      return G_FILE_MONITOR_EVENT_UNMOUNTED;
Packit ae235b
    case IN_Q_OVERFLOW:
Packit ae235b
    case IN_OPEN:
Packit ae235b
    case IN_CLOSE_NOWRITE:
Packit ae235b
    case IN_ACCESS:
Packit ae235b
    case IN_IGNORED:
Packit ae235b
    default:
Packit ae235b
      return -1;
Packit ae235b
    }
Packit ae235b
}