mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-30 17:36:16 +01:00
ac78d14125
After the big file monitoring rewrite, we only put the IN_MOVED_FROM event in the queue for such pairs. It matches INOTIFY_DIR_MASK and thus we call ip_dispatch_event on it, but that function was filtering it out because the filename in the 'from' event is the one of the temp file, not the one we are monitoring. That name is in the 'to' event, so compare it as well, and let the event passin that case. There is another instance of this check in glocalfilemonitor.c, which is corrected here as well. https://bugzilla.gnome.org/show_bug.cgi?id=751358
595 lines
15 KiB
C
595 lines
15 KiB
C
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
|
|
|
|
/* inotify-path.c - GVFS Directory Monitor based on inotify.
|
|
|
|
Copyright (C) 2006 John McCutchan
|
|
Copyright (C) 2009 Codethink Limited
|
|
|
|
The Gnome Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
The Gnome Library 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with the Gnome Library; see the file COPYING.LIB. If not,
|
|
see <http://www.gnu.org/licenses/>.
|
|
|
|
Authors:
|
|
John McCutchan <john@johnmccutchan.com>
|
|
Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
/* Don't put conflicting kernel types in the global namespace: */
|
|
#define __KERNEL_STRICT_NAMES
|
|
|
|
#include <sys/inotify.h>
|
|
#include <string.h>
|
|
#include <glib.h>
|
|
#include "inotify-kernel.h"
|
|
#include "inotify-path.h"
|
|
#include "inotify-missing.h"
|
|
|
|
#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)
|
|
|
|
#define IP_INOTIFY_FILE_MASK (IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE)
|
|
|
|
/* Older libcs don't have this */
|
|
#ifndef IN_ONLYDIR
|
|
#define IN_ONLYDIR 0
|
|
#endif
|
|
|
|
typedef struct ip_watched_file_s {
|
|
gchar *filename;
|
|
gchar *path;
|
|
gint32 wd;
|
|
|
|
GList *subs;
|
|
} ip_watched_file_t;
|
|
|
|
typedef struct ip_watched_dir_s {
|
|
char *path;
|
|
/* TODO: We need to maintain a tree of watched directories
|
|
* so that we can deliver move/delete events to sub folders.
|
|
* Or the application could do it...
|
|
*/
|
|
struct ip_watched_dir_s* parent;
|
|
GList* children;
|
|
|
|
/* basename -> ip_watched_file_t
|
|
* Maps basename to a ip_watched_file_t if the file is currently
|
|
* being directly watched for changes (ie: 'hardlinks' mode).
|
|
*/
|
|
GHashTable *files_hash;
|
|
|
|
/* Inotify state */
|
|
gint32 wd;
|
|
|
|
/* List of inotify subscriptions */
|
|
GList *subs;
|
|
} ip_watched_dir_t;
|
|
|
|
static gboolean ip_debug_enabled = FALSE;
|
|
#define IP_W if (ip_debug_enabled) g_warning
|
|
|
|
/* path -> ip_watched_dir */
|
|
static GHashTable * path_dir_hash = NULL;
|
|
/* inotify_sub * -> ip_watched_dir *
|
|
*
|
|
* Each subscription is attached to a watched directory or it is on
|
|
* the missing list
|
|
*/
|
|
static GHashTable * sub_dir_hash = NULL;
|
|
/* This hash holds GLists of ip_watched_dir_t *'s
|
|
* We need to hold a list because symbolic links can share
|
|
* the same wd
|
|
*/
|
|
static GHashTable * wd_dir_hash = NULL;
|
|
/* This hash holds GLists of ip_watched_file_t *'s
|
|
* We need to hold a list because links can share
|
|
* the same wd
|
|
*/
|
|
static GHashTable * wd_file_hash = NULL;
|
|
|
|
static ip_watched_dir_t *ip_watched_dir_new (const char *path,
|
|
int wd);
|
|
static void ip_watched_dir_free (ip_watched_dir_t *dir);
|
|
static gboolean ip_event_callback (ik_event_t *event);
|
|
|
|
|
|
static gboolean (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
|
|
|
|
gboolean
|
|
_ip_startup (gboolean (*cb)(ik_event_t *event, inotify_sub *sub, gboolean file_event))
|
|
{
|
|
static gboolean initialized = FALSE;
|
|
static gboolean result = FALSE;
|
|
|
|
if (initialized == TRUE)
|
|
return result;
|
|
|
|
event_callback = cb;
|
|
result = _ik_startup (ip_event_callback);
|
|
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
|
|
sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
wd_file_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
initialized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ip_map_path_dir (const char *path,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
g_assert (path && dir);
|
|
g_hash_table_insert (path_dir_hash, dir->path, dir);
|
|
}
|
|
|
|
static void
|
|
ip_map_sub_dir (inotify_sub *sub,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
/* Associate subscription and directory */
|
|
g_assert (dir && sub);
|
|
g_hash_table_insert (sub_dir_hash, sub, dir);
|
|
dir->subs = g_list_prepend (dir->subs, sub);
|
|
}
|
|
|
|
static void
|
|
ip_map_wd_dir (gint32 wd,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
GList *dir_list;
|
|
|
|
g_assert (wd >= 0 && dir);
|
|
dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
|
|
dir_list = g_list_prepend (dir_list, dir);
|
|
g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
|
|
}
|
|
|
|
static void
|
|
ip_map_wd_file (gint32 wd,
|
|
ip_watched_file_t *file)
|
|
{
|
|
GList *file_list;
|
|
|
|
g_assert (wd >= 0 && file);
|
|
file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
|
|
file_list = g_list_prepend (file_list, file);
|
|
g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
|
|
}
|
|
|
|
static void
|
|
ip_unmap_wd_file (gint32 wd,
|
|
ip_watched_file_t *file)
|
|
{
|
|
GList *file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (wd));
|
|
|
|
if (!file_list)
|
|
return;
|
|
|
|
g_assert (wd >= 0 && file);
|
|
file_list = g_list_remove (file_list, file);
|
|
if (file_list == NULL)
|
|
g_hash_table_remove (wd_file_hash, GINT_TO_POINTER (wd));
|
|
else
|
|
g_hash_table_replace (wd_file_hash, GINT_TO_POINTER (wd), file_list);
|
|
}
|
|
|
|
|
|
static ip_watched_file_t *
|
|
ip_watched_file_new (const gchar *dirname,
|
|
const gchar *filename)
|
|
{
|
|
ip_watched_file_t *file;
|
|
|
|
file = g_new0 (ip_watched_file_t, 1);
|
|
file->path = g_strjoin ("/", dirname, filename, NULL);
|
|
file->filename = g_strdup (filename);
|
|
file->wd = -1;
|
|
|
|
return file;
|
|
}
|
|
|
|
static void
|
|
ip_watched_file_free (ip_watched_file_t *file)
|
|
{
|
|
g_assert (file->subs == NULL);
|
|
g_free (file->filename);
|
|
g_free (file->path);
|
|
}
|
|
|
|
static void
|
|
ip_watched_file_add_sub (ip_watched_file_t *file,
|
|
inotify_sub *sub)
|
|
{
|
|
file->subs = g_list_prepend (file->subs, sub);
|
|
}
|
|
|
|
static void
|
|
ip_watched_file_start (ip_watched_file_t *file)
|
|
{
|
|
if (file->wd < 0)
|
|
{
|
|
gint err;
|
|
|
|
file->wd = _ik_watch (file->path,
|
|
IP_INOTIFY_FILE_MASK,
|
|
&err);
|
|
|
|
if (file->wd >= 0)
|
|
ip_map_wd_file (file->wd, file);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ip_watched_file_stop (ip_watched_file_t *file)
|
|
{
|
|
if (file->wd >= 0)
|
|
{
|
|
_ik_ignore (file->path, file->wd);
|
|
ip_unmap_wd_file (file->wd, file);
|
|
file->wd = -1;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
_ip_start_watching (inotify_sub *sub)
|
|
{
|
|
gint32 wd;
|
|
int err;
|
|
ip_watched_dir_t *dir;
|
|
|
|
g_assert (sub);
|
|
g_assert (!sub->cancelled);
|
|
g_assert (sub->dirname);
|
|
|
|
IP_W ("Starting to watch %s\n", sub->dirname);
|
|
dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
|
|
|
|
if (dir == NULL)
|
|
{
|
|
IP_W ("Trying to add inotify watch ");
|
|
wd = _ik_watch (sub->dirname, IP_INOTIFY_DIR_MASK|IN_ONLYDIR, &err);
|
|
if (wd < 0)
|
|
{
|
|
IP_W ("Failed\n");
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* Create new watched directory and associate it with the
|
|
* wd hash and path hash
|
|
*/
|
|
IP_W ("Success\n");
|
|
dir = ip_watched_dir_new (sub->dirname, wd);
|
|
ip_map_wd_dir (wd, dir);
|
|
ip_map_path_dir (sub->dirname, dir);
|
|
}
|
|
}
|
|
else
|
|
IP_W ("Already watching\n");
|
|
|
|
if (sub->hardlinks)
|
|
{
|
|
ip_watched_file_t *file;
|
|
|
|
file = g_hash_table_lookup (dir->files_hash, sub->filename);
|
|
|
|
if (file == NULL)
|
|
{
|
|
file = ip_watched_file_new (sub->dirname, sub->filename);
|
|
g_hash_table_insert (dir->files_hash, file->filename, file);
|
|
}
|
|
|
|
ip_watched_file_add_sub (file, sub);
|
|
ip_watched_file_start (file);
|
|
}
|
|
|
|
ip_map_sub_dir (sub, dir);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ip_unmap_path_dir (const char *path,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
g_assert (path && dir);
|
|
g_hash_table_remove (path_dir_hash, dir->path);
|
|
}
|
|
|
|
static void
|
|
ip_unmap_wd_dir (gint32 wd,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
|
|
|
|
if (!dir_list)
|
|
return;
|
|
|
|
g_assert (wd >= 0 && dir);
|
|
dir_list = g_list_remove (dir_list, dir);
|
|
if (dir_list == NULL)
|
|
g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd));
|
|
else
|
|
g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list);
|
|
}
|
|
|
|
static void
|
|
ip_unmap_wd (gint32 wd)
|
|
{
|
|
GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
|
|
if (!dir_list)
|
|
return;
|
|
g_assert (wd >= 0);
|
|
g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd));
|
|
g_list_free (dir_list);
|
|
}
|
|
|
|
static void
|
|
ip_unmap_sub_dir (inotify_sub *sub,
|
|
ip_watched_dir_t *dir)
|
|
{
|
|
g_assert (sub && dir);
|
|
g_hash_table_remove (sub_dir_hash, sub);
|
|
dir->subs = g_list_remove (dir->subs, sub);
|
|
|
|
if (sub->hardlinks)
|
|
{
|
|
ip_watched_file_t *file;
|
|
|
|
file = g_hash_table_lookup (dir->files_hash, sub->filename);
|
|
file->subs = g_list_remove (file->subs, sub);
|
|
|
|
if (file->subs == NULL)
|
|
{
|
|
g_hash_table_remove (dir->files_hash, sub->filename);
|
|
ip_watched_file_stop (file);
|
|
ip_watched_file_free (file);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
ip_unmap_all_subs (ip_watched_dir_t *dir)
|
|
{
|
|
while (dir->subs != NULL)
|
|
ip_unmap_sub_dir (dir->subs->data, dir);
|
|
}
|
|
|
|
gboolean
|
|
_ip_stop_watching (inotify_sub *sub)
|
|
{
|
|
ip_watched_dir_t *dir = NULL;
|
|
|
|
dir = g_hash_table_lookup (sub_dir_hash, sub);
|
|
if (!dir)
|
|
return TRUE;
|
|
|
|
ip_unmap_sub_dir (sub, dir);
|
|
|
|
/* No one is subscribing to this directory any more */
|
|
if (dir->subs == NULL)
|
|
{
|
|
_ik_ignore (dir->path, dir->wd);
|
|
ip_unmap_wd_dir (dir->wd, dir);
|
|
ip_unmap_path_dir (dir->path, dir);
|
|
ip_watched_dir_free (dir);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static ip_watched_dir_t *
|
|
ip_watched_dir_new (const char *path,
|
|
gint32 wd)
|
|
{
|
|
ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1);
|
|
|
|
dir->path = g_strdup (path);
|
|
dir->files_hash = g_hash_table_new (g_str_hash, g_str_equal);
|
|
dir->wd = wd;
|
|
|
|
return dir;
|
|
}
|
|
|
|
static void
|
|
ip_watched_dir_free (ip_watched_dir_t *dir)
|
|
{
|
|
g_assert_cmpint (g_hash_table_size (dir->files_hash), ==, 0);
|
|
g_assert (dir->subs == NULL);
|
|
g_free (dir->path);
|
|
g_hash_table_unref (dir->files_hash);
|
|
g_free (dir);
|
|
}
|
|
|
|
static void
|
|
ip_wd_delete (gpointer data,
|
|
gpointer user_data)
|
|
{
|
|
ip_watched_dir_t *dir = data;
|
|
GList *l = NULL;
|
|
|
|
for (l = dir->subs; l; l = l->next)
|
|
{
|
|
inotify_sub *sub = l->data;
|
|
/* Add subscription to missing list */
|
|
_im_add (sub);
|
|
}
|
|
ip_unmap_all_subs (dir);
|
|
/* Unassociate the path and the directory */
|
|
ip_unmap_path_dir (dir->path, dir);
|
|
ip_watched_dir_free (dir);
|
|
}
|
|
|
|
static gboolean
|
|
ip_event_dispatch (GList *dir_list,
|
|
GList *file_list,
|
|
ik_event_t *event)
|
|
{
|
|
gboolean interesting = FALSE;
|
|
|
|
GList *l;
|
|
|
|
if (!event)
|
|
return FALSE;
|
|
|
|
for (l = dir_list; l; l = l->next)
|
|
{
|
|
GList *subl;
|
|
ip_watched_dir_t *dir = l->data;
|
|
|
|
for (subl = dir->subs; subl; subl = subl->next)
|
|
{
|
|
inotify_sub *sub = subl->data;
|
|
|
|
/* If the subscription and the event
|
|
* contain a filename and they don't
|
|
* match, we don't deliver this event.
|
|
*/
|
|
if (sub->filename &&
|
|
event->name &&
|
|
strcmp (sub->filename, event->name) &&
|
|
(!event->pair || !event->pair->name || strcmp (sub->filename, event->pair->name)))
|
|
continue;
|
|
|
|
/* If the subscription has a filename
|
|
* but this event doesn't, we don't
|
|
* deliver this event.
|
|
*/
|
|
if (sub->filename && !event->name)
|
|
continue;
|
|
|
|
/* If we're also watching the file directly
|
|
* don't report events that will also be
|
|
* reported on the file itself.
|
|
*/
|
|
if (sub->hardlinks)
|
|
{
|
|
event->mask &= ~IP_INOTIFY_FILE_MASK;
|
|
if (!event->mask)
|
|
continue;
|
|
}
|
|
|
|
/* FIXME: We might need to synthesize
|
|
* DELETE/UNMOUNT events when
|
|
* the filename doesn't match
|
|
*/
|
|
|
|
interesting |= event_callback (event, sub, FALSE);
|
|
|
|
if (sub->hardlinks)
|
|
{
|
|
ip_watched_file_t *file;
|
|
|
|
file = g_hash_table_lookup (dir->files_hash, sub->filename);
|
|
|
|
if (file != NULL)
|
|
{
|
|
if (event->mask & (IN_MOVED_FROM | IN_DELETE))
|
|
ip_watched_file_stop (file);
|
|
|
|
if (event->mask & (IN_MOVED_TO | IN_CREATE))
|
|
ip_watched_file_start (file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (l = file_list; l; l = l->next)
|
|
{
|
|
ip_watched_file_t *file = l->data;
|
|
GList *subl;
|
|
|
|
for (subl = file->subs; subl; subl = subl->next)
|
|
{
|
|
inotify_sub *sub = subl->data;
|
|
|
|
interesting |= event_callback (event, sub, TRUE);
|
|
}
|
|
}
|
|
|
|
return interesting;
|
|
}
|
|
|
|
static gboolean
|
|
ip_event_callback (ik_event_t *event)
|
|
{
|
|
gboolean interesting = FALSE;
|
|
GList* dir_list = NULL;
|
|
GList *file_list = NULL;
|
|
|
|
/* We can ignore the IGNORED events */
|
|
if (event->mask & IN_IGNORED)
|
|
{
|
|
_ik_event_free (event);
|
|
return TRUE;
|
|
}
|
|
|
|
dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd));
|
|
file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->wd));
|
|
|
|
if (event->mask & IP_INOTIFY_DIR_MASK)
|
|
interesting |= ip_event_dispatch (dir_list, file_list, event);
|
|
|
|
/* Only deliver paired events if the wds are separate */
|
|
if (event->pair && event->pair->wd != event->wd)
|
|
{
|
|
dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd));
|
|
file_list = g_hash_table_lookup (wd_file_hash, GINT_TO_POINTER (event->pair->wd));
|
|
|
|
if (event->pair->mask & IP_INOTIFY_DIR_MASK)
|
|
interesting |= ip_event_dispatch (dir_list, file_list, event->pair);
|
|
}
|
|
|
|
/* We have to manage the missing list
|
|
* when we get an event that means the
|
|
* file has been deleted/moved/unmounted.
|
|
*/
|
|
if (event->mask & IN_DELETE_SELF ||
|
|
event->mask & IN_MOVE_SELF ||
|
|
event->mask & IN_UNMOUNT)
|
|
{
|
|
/* Add all subscriptions to missing list */
|
|
g_list_foreach (dir_list, ip_wd_delete, NULL);
|
|
/* Unmap all directories attached to this wd */
|
|
ip_unmap_wd (event->wd);
|
|
}
|
|
|
|
_ik_event_free (event);
|
|
|
|
return interesting;
|
|
}
|
|
|
|
const char *
|
|
_ip_get_path_for_wd (gint32 wd)
|
|
{
|
|
GList *dir_list;
|
|
ip_watched_dir_t *dir;
|
|
|
|
g_assert (wd >= 0);
|
|
dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd));
|
|
if (dir_list)
|
|
{
|
|
dir = dir_list->data;
|
|
if (dir)
|
|
return dir->path;
|
|
}
|
|
|
|
return NULL;
|
|
}
|