(root)/
glib-2.79.0/
gio/
inotify/
inotify-path.c
       1  /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
       2  
       3  /* inotify-path.c - GVFS Monitor based on inotify.
       4  
       5     Copyright (C) 2006 John McCutchan
       6     Copyright (C) 2009 Codethink Limited
       7  
       8     This library is free software; you can redistribute it and/or
       9     modify it under the terms of the GNU Lesser General Public
      10     License as published by the Free Software Foundation; either
      11     version 2.1 of the License, or (at your option) any later version.
      12  
      13     This library is distributed in the hope that it will be useful,
      14     but WITHOUT ANY WARRANTY; without even the implied warranty of
      15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      16     Lesser General Public License for more details.
      17  
      18     You should have received a copy of the GNU Lesser General Public License
      19     along with this library; if not, see <http://www.gnu.org/licenses/>.
      20  
      21     Authors:
      22  		 John McCutchan <john@johnmccutchan.com>
      23                   Ryan Lortie <desrt@desrt.ca>
      24  */
      25  
      26  #include "config.h"
      27  
      28  /* Don't put conflicting kernel types in the global namespace: */
      29  #define __KERNEL_STRICT_NAMES
      30  
      31  #include <sys/inotify.h>
      32  #include <string.h>
      33  #include <glib.h>
      34  #include "inotify-kernel.h"
      35  #include "inotify-path.h"
      36  #include "inotify-missing.h"
      37  
      38  #define IP_INOTIFY_DIR_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF|IN_CLOSE_WRITE)
      39  
      40  #define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
      41  
      42  /* Older libcs don't have this */
      43  #ifndef IN_ONLYDIR
      44  #define IN_ONLYDIR 0  
      45  #endif
      46  
      47  typedef struct ip_watched_file_s {
      48    gchar *filename;
      49    gchar *path;
      50    gint32 wd;
      51  
      52    GList *subs;
      53  } ip_watched_file_t;
      54  
      55  typedef struct ip_watched_dir_s {
      56    char *path;
      57    /* TODO: We need to maintain a tree of watched directories
      58     * so that we can deliver move/delete events to sub folders.
      59     * Or the application could do it...
      60     */
      61    struct ip_watched_dir_s* parent;
      62    GList*	 children;
      63  
      64    /* basename -> ip_watched_file_t
      65     * Maps basename to a ip_watched_file_t if the file is currently
      66     * being directly watched for changes (ie: 'hardlinks' mode).
      67     */
      68    GHashTable *files_hash;
      69  
      70    /* Inotify state */
      71    gint32 wd;
      72    
      73    /* List of inotify subscriptions */
      74    GList *subs;
      75  } ip_watched_dir_t;
      76  
      77  static gboolean     ip_debug_enabled = FALSE;
      78  #define IP_W if (ip_debug_enabled) g_warning
      79  
      80  /* path -> ip_watched_dir */
      81  static GHashTable * path_dir_hash = NULL;
      82  /* inotify_sub * -> ip_watched_dir *
      83   *
      84   * Each subscription is attached to a watched directory or it is on
      85   * the missing list
      86   */
      87  static GHashTable * sub_dir_hash = NULL;
      88  /* This hash holds GLists of ip_watched_dir_t *'s
      89   * We need to hold a list because symbolic links can share
      90   * the same wd
      91   */
      92  static GHashTable * wd_dir_hash = NULL;
      93  /* This hash holds GLists of ip_watched_file_t *'s
      94   * We need to hold a list because links can share
      95   * the same wd
      96   */
      97  static GHashTable * wd_file_hash = NULL;
      98  
      99  static ip_watched_dir_t *ip_watched_dir_new  (const char       *path,
     100  					      int               wd);
     101  static void              ip_watched_dir_free (ip_watched_dir_t *dir);
     102  static gboolean          ip_event_callback   (ik_event_t       *event);
     103  
     104  
     105  static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
     106  
     107  gboolean
     108  _ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
     109  {
     110    static gboolean initialized = FALSE;
     111    static gboolean result = FALSE;
     112    
     113    if (initialized == TRUE)
     114      return result;
     115  
     116    event_callback = cb;
     117    result = _ik_startup (ip_event_callback);
     118  
     119    if (!result)
     120      return FALSE;
     121  
     122    path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
     123    sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
     124    wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
     125    wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
     126    
     127    initialized = TRUE;
     128    return TRUE;
     129  }
     130  
     131  static void
     132  ip_map_path_dir (const char       *path, 
     133                   ip_watched_dir_t *dir)
     134  {
     135    g_assert (path && dir);
     136    g_hash_table_insert (path_dir_hash, dir->path, dir);
     137  }
     138  
     139  static void
     140  ip_map_sub_dir (inotify_sub      *sub, 
     141                  ip_watched_dir_t *dir)
     142  {
     143    /* Associate subscription and directory */
     144    g_assert (dir && sub);
     145    g_hash_table_insert (sub_dir_hash, sub, dir);
     146    dir->subs = g_list_prepend (dir->subs, sub);
     147  }
     148  
     149  static void
     150  ip_map_wd_dir (gint32            wd, 
     151                 ip_watched_dir_t *dir)
     152  {
     153    GList *dir_list;
     154    
     155    g_assert (wd >= 0 && dir);
     156    dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
     157    dir_list = g_list_prepend (dir_list, dir);
     158    g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
     159  }
     160  
     161  static void
     162  ip_map_wd_file (gint32             wd,
     163                  ip_watched_file_t *file)
     164  {
     165    GList *file_list;
     166  
     167    g_assert (wd >= 0 && file);
     168    file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
     169    file_list = g_list_prepend (file_list, file);
     170    g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
     171  }
     172  
     173  static void
     174  ip_unmap_wd_file (gint32             wd,
     175                    ip_watched_file_t *file)
     176  {
     177    GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
     178  
     179    if (!file_list)
     180      return;
     181  
     182    g_assert (wd >= 0 && file);
     183    file_list = g_list_remove (file_list, file);
     184    if (file_list == NULL)
     185      g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
     186    else
     187      g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
     188  }
     189  
     190  
     191  static ip_watched_file_t *
     192  ip_watched_file_new (const gchar *dirname,
     193                       const gchar *filename)
     194  {
     195    ip_watched_file_t *file;
     196  
     197    file = g_new0 (ip_watched_file_t, 1);
     198    file->path = g_strjoin ("/", dirname, filename, NULL);
     199    file->filename = g_strdup (filename);
     200    file->wd = -1;
     201  
     202    return file;
     203  }
     204  
     205  static void
     206  ip_watched_file_free (ip_watched_file_t *file)
     207  {
     208    g_assert (file->subs == NULL);
     209    g_free (file->filename);
     210    g_free (file->path);
     211    g_free (file);
     212  }
     213  
     214  static void
     215  ip_watched_file_add_sub (ip_watched_file_t *file,
     216                           inotify_sub       *sub)
     217  {
     218    file->subs = g_list_prepend (file->subs, sub);
     219  }
     220  
     221  static void
     222  ip_watched_file_start (ip_watched_file_t *file)
     223  {
     224    if (file->wd < 0)
     225      {
     226        gint err;
     227  
     228        file->wd = _ik_watch (file->path,
     229                              IP_INOTIFY_FILE_MASK,
     230                              &err);
     231  
     232        if (file->wd >= 0)
     233          ip_map_wd_file (file->wd, file);
     234      }
     235  }
     236  
     237  static void
     238  ip_watched_file_stop (ip_watched_file_t *file)
     239  {
     240    if (file->wd >= 0)
     241      {
     242        _ik_ignore (file->path, file->wd);
     243        ip_unmap_wd_file (file->wd, file);
     244        file->wd = -1;
     245      }
     246  }
     247  
     248  gboolean
     249  _ip_start_watching (inotify_sub *sub)
     250  {
     251    gint32 wd;
     252    int err;
     253    ip_watched_dir_t *dir;
     254    
     255    g_assert (sub);
     256    g_assert (!sub->cancelled);
     257    g_assert (sub->dirname);
     258    
     259    IP_W ("Starting to watch %s\n", sub->dirname);
     260    dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
     261  
     262    if (dir == NULL)
     263      {
     264        IP_W ("Trying to add inotify watch ");
     265        wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
     266        if (wd < 0)
     267          {
     268            IP_W ("Failed\n");
     269            return FALSE;
     270          }
     271        else
     272          {
     273            /* Create new watched directory and associate it with the
     274             * wd hash and path hash
     275             */
     276            IP_W ("Success\n");
     277            dir = ip_watched_dir_new (sub->dirname, wd);
     278            ip_map_wd_dir (wd, dir);
     279            ip_map_path_dir (sub->dirname, dir);
     280          }
     281      }
     282    else
     283      IP_W ("Already watching\n");
     284  
     285    if (sub->hardlinks)
     286      {
     287        ip_watched_file_t *file;
     288  
     289        file = g_hash_table_lookup (dir->files_hash, sub->filename);
     290  
     291        if (file == NULL)
     292          {
     293            file = ip_watched_file_new (sub->dirname, sub->filename);
     294            g_hash_table_insert (dir->files_hash, file->filename, file);
     295          }
     296  
     297        ip_watched_file_add_sub (file, sub);
     298        ip_watched_file_start (file);
     299      }
     300  
     301    ip_map_sub_dir (sub, dir);
     302    
     303    return TRUE;
     304  }
     305  
     306  static void
     307  ip_unmap_path_dir (const char       *path, 
     308                     ip_watched_dir_t *dir)
     309  {
     310    g_assert (path && dir);
     311    g_hash_table_remove (path_dir_hash, dir->path);
     312  }
     313  
     314  static void
     315  ip_unmap_wd_dir (gint32            wd, 
     316                   ip_watched_dir_t *dir)
     317  {
     318    GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
     319    
     320    if (!dir_list)
     321      return;
     322    
     323    g_assert (wd >= 0 && dir);
     324    dir_list = g_list_remove (dir_list, dir);
     325    if (dir_list == NULL) 
     326      g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
     327    else
     328      g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
     329  }
     330  
     331  static void
     332  ip_unmap_wd (gint32 wd)
     333  {
     334    GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
     335    if (!dir_list)
     336      return;
     337    g_assert (wd >= 0);
     338    g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
     339    g_list_free (dir_list);
     340  }
     341  
     342  static void
     343  ip_unmap_sub_dir (inotify_sub      *sub,
     344                    ip_watched_dir_t *dir)
     345  {
     346    g_assert (sub && dir);
     347    g_hash_table_remove (sub_dir_hash, sub);
     348    dir->subs = g_list_remove (dir->subs, sub);
     349  
     350    if (sub->hardlinks)
     351      {
     352        ip_watched_file_t *file;
     353  
     354        file = g_hash_table_lookup (dir->files_hash, sub->filename);
     355        file->subs = g_list_remove (file->subs, sub);
     356  
     357        if (file->subs == NULL)
     358          {
     359            g_hash_table_remove (dir->files_hash, sub->filename);
     360            ip_watched_file_stop (file);
     361            ip_watched_file_free (file);
     362          }
     363      }
     364   }
     365  
     366  static void
     367  ip_unmap_all_subs (ip_watched_dir_t *dir)
     368  {
     369    while (dir->subs != NULL)
     370      ip_unmap_sub_dir (dir->subs->data, dir);
     371  }
     372  
     373  gboolean
     374  _ip_stop_watching (inotify_sub *sub)
     375  {
     376    ip_watched_dir_t *dir = NULL;
     377    
     378    dir = g_hash_table_lookup (sub_dir_hash, sub);
     379    if (!dir) 
     380      return TRUE;
     381    
     382    ip_unmap_sub_dir (sub, dir);
     383    
     384    /* No one is subscribing to this directory any more */
     385    if (dir->subs == NULL)
     386      {
     387        _ik_ignore (dir->path, dir->wd);
     388        ip_unmap_wd_dir (dir->wd, dir);
     389        ip_unmap_path_dir (dir->path, dir);
     390        ip_watched_dir_free (dir);
     391      }
     392    
     393    return TRUE;
     394  }
     395  
     396  
     397  static ip_watched_dir_t *
     398  ip_watched_dir_new (const char *path, 
     399                      gint32      wd)
     400  {
     401    ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
     402    
     403    dir->path = g_strdup (path);
     404    dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
     405    dir->wd = wd;
     406    
     407    return dir;
     408  }
     409  
     410  static void
     411  ip_watched_dir_free (ip_watched_dir_t *dir)
     412  {
     413    g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
     414    g_assert (dir->subs == NULL);
     415    g_free (dir->path);
     416    g_hash_table_unref (dir->files_hash);
     417    g_free (dir);
     418  }
     419  
     420  static void
     421  ip_wd_delete (gpointer data, 
     422                gpointer user_data)
     423  {
     424    ip_watched_dir_t *dir = data;
     425    GList *l = NULL;
     426    
     427    for (l = dir->subs; l; l = l->next)
     428      {
     429        inotify_sub *sub = l->data;
     430        /* Add subscription to missing list */
     431        _im_add (sub);
     432      }
     433    ip_unmap_all_subs (dir);
     434    /* Unassociate the path and the directory */
     435    ip_unmap_path_dir (dir->path, dir);
     436    ip_watched_dir_free (dir);
     437  }
     438  
     439  static gboolean
     440  ip_event_dispatch (GList      *dir_list, 
     441                     GList      *file_list,
     442                     ik_event_t *event)
     443  {
     444    gboolean interesting = FALSE;
     445  
     446    GList *l;
     447    
     448    if (!event)
     449      return FALSE;
     450  
     451    for (l = dir_list; l; l = l->next)
     452      {
     453        GList *subl;
     454        ip_watched_dir_t *dir = l->data;
     455        
     456        for (subl = dir->subs; subl; subl = subl->next)
     457  	{
     458  	  inotify_sub *sub = subl->data;
     459  	  
     460  	  /* If the subscription and the event
     461  	   * contain a filename and they don't
     462  	   * match, we don't deliver this event.
     463  	   */
     464  	  if (sub->filename &&
     465  	      event->name &&
     466  	      strcmp (sub->filename, event->name) &&
     467                (!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
     468  	    continue;
     469  	  
     470  	  /* If the subscription has a filename
     471  	   * but this event doesn't, we don't
     472  	   * deliver this event.
     473  	   */
     474  	  if (sub->filename && !event->name)
     475  	    continue;
     476  	  
     477  	  /* If we're also watching the file directly
     478  	   * don't report events that will also be
     479  	   * reported on the file itself.
     480  	   */
     481  	  if (sub->hardlinks)
     482  	    {
     483  	      event->mask &= ~IP_INOTIFY_FILE_MASK;
     484  	      if (!event->mask)
     485  		continue;
     486  	    }
     487  	  
     488  	  /* FIXME: We might need to synthesize
     489  	   * DELETE/UNMOUNT events when
     490  	   * the filename doesn't match
     491  	   */
     492  	  
     493  	  interesting |= event_callback (event, sub, FALSE);
     494  
     495            if (sub->hardlinks)
     496              {
     497                ip_watched_file_t *file;
     498  
     499                file = g_hash_table_lookup (dir->files_hash, sub->filename);
     500  
     501                if (file != NULL)
     502                  {
     503                    if (event->mask & (IN_MOVED_FROM | IN_DELETE))
     504                      ip_watched_file_stop (file);
     505  
     506                    if (event->mask & (IN_MOVED_TO | IN_CREATE))
     507                      ip_watched_file_start (file);
     508                  }
     509              }
     510          }
     511      }
     512  
     513    for (l = file_list; l; l = l->next)
     514      {
     515        ip_watched_file_t *file = l->data;
     516        GList *subl;
     517  
     518        for (subl = file->subs; subl; subl = subl->next)
     519          {
     520  	  inotify_sub *sub = subl->data;
     521  
     522  	  interesting |= event_callback (event, sub, TRUE);
     523          }
     524      }
     525  
     526    return interesting;
     527  }
     528  
     529  static gboolean
     530  ip_event_callback (ik_event_t *event)
     531  {
     532    gboolean interesting = FALSE;
     533    GList* dir_list = NULL;
     534    GList *file_list = NULL;
     535  
     536    /* We can ignore the IGNORED events. Likewise, if the event queue overflowed,
     537     * there is not much we can do to recover. */
     538    if (event->mask & (IN_IGNORED | IN_Q_OVERFLOW))
     539      {
     540        _ik_event_free (event);
     541        return TRUE;
     542      }
     543  
     544    dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
     545    file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
     546  
     547    if (event->mask & IP_INOTIFY_DIR_MASK)
     548      interesting |= ip_event_dispatch (dir_list, file_list, event);
     549  
     550    /* Only deliver paired events if the wds are separate */
     551    if (event->pair && event->pair->wd != event->wd)
     552      {
     553        dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
     554        file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
     555  
     556        if (event->pair->mask & IP_INOTIFY_DIR_MASK)
     557          interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
     558      }
     559  
     560    /* We have to manage the missing list
     561     * when we get an event that means the
     562     * file has been deleted/moved/unmounted.
     563     */
     564    if (event->mask & IN_DELETE_SELF ||
     565        event->mask & IN_MOVE_SELF ||
     566        event->mask & IN_UNMOUNT)
     567      {
     568        /* Add all subscriptions to missing list */
     569        g_list_foreach (dir_list, ip_wd_delete, NULL);
     570        /* Unmap all directories attached to this wd */
     571        ip_unmap_wd (event->wd);
     572      }
     573    
     574    _ik_event_free (event);
     575  
     576    return interesting;
     577  }
     578  
     579  const char *
     580  _ip_get_path_for_wd (gint32 wd)
     581  {
     582    GList *dir_list;
     583    ip_watched_dir_t *dir;
     584  
     585    g_assert (wd >= 0);
     586    dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
     587    if (dir_list)
     588      {
     589        dir = dir_list->data;
     590        if (dir)
     591  	return dir->path;
     592      }
     593  
     594    return NULL;
     595  }