(root)/
glib-2.79.0/
gio/
inotify/
inotify-helper.c
       1  /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
       2  
       3  /* inotify-helper.c - GVFS Monitor based on inotify.
       4  
       5     Copyright (C) 2007 John McCutchan
       6  
       7     This library is free software; you can redistribute it and/or
       8     modify it under the terms of the GNU Lesser General Public
       9     License as published by the Free Software Foundation; either
      10     version 2.1 of the License, or (at your option) any later version.
      11  
      12     This library is distributed in the hope that it will be useful,
      13     but WITHOUT ANY WARRANTY; without even the implied warranty of
      14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15     Lesser General Public License for more details.
      16  
      17     You should have received a copy of the GNU Lesser General Public License
      18     along with this library; if not, see <http://www.gnu.org/licenses/>.
      19  
      20     Authors: 
      21  		 John McCutchan <john@johnmccutchan.com>
      22  */
      23  
      24  #include "config.h"
      25  #include <errno.h>
      26  #include <time.h>
      27  #include <string.h>
      28  #include <sys/ioctl.h>
      29  #include <sys/stat.h>
      30  /* Just include the local header to stop all the pain */
      31  #include <sys/inotify.h>
      32  #include <gio/glocalfilemonitor.h>
      33  #include <gio/gfile.h>
      34  #include "inotify-helper.h"
      35  #include "inotify-missing.h"
      36  #include "inotify-path.h"
      37  
      38  static gboolean ih_debug_enabled = FALSE;
      39  #define IH_W if (ih_debug_enabled) g_warning 
      40  
      41  static gboolean ih_event_callback (ik_event_t  *event,
      42                                     inotify_sub *sub,
      43                                     gboolean     file_event);
      44  static void ih_not_missing_callback (inotify_sub *sub);
      45  
      46  /* We share this lock with inotify-kernel.c and inotify-missing.c
      47   *
      48   * inotify-kernel.c takes the lock when it reads events from
      49   * the kernel and when it processes those events
      50   *
      51   * inotify-missing.c takes the lock when it is scanning the missing
      52   * list.
      53   *
      54   * We take the lock in all public functions
      55   */
      56  G_LOCK_DEFINE (inotify_lock);
      57  
      58  static GFileMonitorEvent ih_mask_to_EventFlags (guint32 mask);
      59  
      60  /**
      61   * _ih_startup:
      62   *
      63   * Initializes the inotify backend.  This must be called before
      64   * any other functions in this module.
      65   *
      66   * Returns: #TRUE if initialization succeeded, #FALSE otherwise
      67   */
      68  gboolean
      69  _ih_startup (void)
      70  {
      71    static gboolean initialized = FALSE;
      72    static gboolean result = FALSE;
      73    
      74    G_LOCK (inotify_lock);
      75    
      76    if (initialized == TRUE)
      77      {
      78        G_UNLOCK (inotify_lock);
      79        return result;
      80      }
      81  
      82    result = _ip_startup (ih_event_callback);
      83    if (!result)
      84      {
      85        G_UNLOCK (inotify_lock);
      86        return FALSE;
      87      }
      88    _im_startup (ih_not_missing_callback);
      89  
      90    IH_W ("started gvfs inotify backend\n");
      91    
      92    initialized = TRUE;
      93    
      94    G_UNLOCK (inotify_lock);
      95    
      96    return TRUE;
      97  }
      98  
      99  /*
     100   * Adds a subscription to be monitored.
     101   */
     102  gboolean
     103  _ih_sub_add (inotify_sub *sub)
     104  {
     105    G_LOCK (inotify_lock);
     106  	
     107    if (!_ip_start_watching (sub))
     108      _im_add (sub);
     109    
     110    G_UNLOCK (inotify_lock);
     111  
     112    return TRUE;
     113  }
     114  
     115  /*
     116   * Cancels a subscription which was being monitored.
     117   */
     118  gboolean
     119  _ih_sub_cancel (inotify_sub *sub)
     120  {
     121    G_LOCK (inotify_lock);
     122  
     123    if (!sub->cancelled)
     124      {
     125        IH_W ("cancelling %s\n", sub->dirname);
     126        sub->cancelled = TRUE;
     127        _im_rm (sub);
     128        _ip_stop_watching (sub);
     129      }
     130    
     131    G_UNLOCK (inotify_lock);
     132  
     133    return TRUE;
     134  }
     135  
     136  static char *
     137  _ih_fullpath_from_event (ik_event_t *event,
     138  			 const char *dirname,
     139  			 const char *filename)
     140  {
     141    char *fullpath;
     142  
     143    if (filename)
     144      fullpath = g_strdup_printf ("%s/%s", dirname, filename);
     145    else if (event->name)
     146      fullpath = g_strdup_printf ("%s/%s", dirname, event->name);
     147    else
     148      fullpath = g_strdup_printf ("%s/", dirname);
     149  
     150     return fullpath;
     151  }
     152  
     153  static gboolean
     154  ih_event_callback (ik_event_t  *event,
     155                     inotify_sub *sub,
     156                     gboolean     file_event)
     157  {
     158    gboolean interesting;
     159    GFileMonitorEvent event_flags;
     160  
     161    event_flags = ih_mask_to_EventFlags (event->mask);
     162  
     163    if (event->mask & IN_MOVE)
     164      {
     165        /* We either have a rename (in the same directory) or a move
     166         * (between different directories).
     167         */
     168        if (event->pair && event->pair->wd == event->wd)
     169          {
     170            /* this is a rename */
     171            interesting = g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
     172                                                              event->name, event->pair->name, NULL, event->timestamp);
     173          }
     174        else
     175          {
     176            GFile *other;
     177  
     178            if (event->pair)
     179              {
     180                const char *parent_dir;
     181                gchar *fullpath;
     182  
     183                parent_dir = _ip_get_path_for_wd (event->pair->wd);
     184                fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
     185                other = g_file_new_for_path (fullpath);
     186                g_free (fullpath);
     187              }
     188            else
     189              other = NULL;
     190  
     191            /* This is either an incoming or outgoing move. Since we checked the
     192             * event->mask above, it should have converted to a #GFileMonitorEvent
     193             * properly. If not, the assumption we have made about event->mask
     194             * only ever having a single bit set (apart from IN_ISDIR) is false.
     195             * The kernel documentation is lacking here. */
     196            g_assert ((int) event_flags != -1);
     197            interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
     198                                                              event->name, NULL, other, event->timestamp);
     199  
     200            if (other)
     201              g_object_unref (other);
     202          }
     203      }
     204    else if ((int) event_flags != -1)
     205      /* unpaired event -- no 'other' field */
     206      interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
     207                                                        event->name, NULL, NULL, event->timestamp);
     208    else
     209      interesting = FALSE;
     210  
     211    if (event->mask & IN_CREATE)
     212      {
     213        const gchar *parent_dir;
     214        gchar *fullname;
     215        struct stat buf;
     216        gint s;
     217  
     218        /* The kernel reports IN_CREATE for two types of events:
     219         *
     220         *  - creat(), in which case IN_CLOSE_WRITE will come soon; or
     221         *  - link(), mkdir(), mknod(), etc., in which case it won't
     222         *
     223         * We can attempt to detect the second case and send the
     224         * CHANGES_DONE immediately so that the user isn't left waiting.
     225         *
     226         * The detection for link() is not 100% reliable since the link
     227         * count could be 1 if the original link was deleted or if
     228         * O_TMPFILE was being used, but in that case the virtual
     229         * CHANGES_DONE will be emitted to close the loop.
     230         */
     231  
     232        parent_dir = _ip_get_path_for_wd (event->wd);
     233        fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
     234        s = stat (fullname, &buf);
     235        g_free (fullname);
     236  
     237        /* if it doesn't look like the result of creat()... */
     238        if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
     239          g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
     240                                              event->name, NULL, NULL, event->timestamp);
     241      }
     242  
     243    return interesting;
     244  }
     245  
     246  static void
     247  ih_not_missing_callback (inotify_sub *sub)
     248  {
     249    gint now = g_get_monotonic_time ();
     250  
     251    g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
     252                                        sub->filename, NULL, NULL, now);
     253    g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
     254                                        sub->filename, NULL, NULL, now);
     255  }
     256  
     257  /* Transforms a inotify event to a GVFS event. */
     258  static GFileMonitorEvent
     259  ih_mask_to_EventFlags (guint32 mask)
     260  {
     261    mask &= ~IN_ISDIR;
     262    switch (mask)
     263      {
     264      case IN_MODIFY:
     265        return G_FILE_MONITOR_EVENT_CHANGED;
     266      case IN_CLOSE_WRITE:
     267        return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT;
     268      case IN_ATTRIB:
     269        return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
     270      case IN_MOVE_SELF:
     271      case IN_DELETE:
     272      case IN_DELETE_SELF:
     273        return G_FILE_MONITOR_EVENT_DELETED;
     274      case IN_CREATE:
     275        return G_FILE_MONITOR_EVENT_CREATED;
     276      case IN_MOVED_FROM:
     277        return G_FILE_MONITOR_EVENT_MOVED_OUT;
     278      case IN_MOVED_TO:
     279        return G_FILE_MONITOR_EVENT_MOVED_IN;
     280      case IN_UNMOUNT:
     281        return G_FILE_MONITOR_EVENT_UNMOUNTED;
     282      case IN_Q_OVERFLOW:
     283      case IN_OPEN:
     284      case IN_CLOSE_NOWRITE:
     285      case IN_ACCESS:
     286      case IN_IGNORED:
     287      default:
     288        return -1;
     289      }
     290  }