mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-09 18:54:04 +02:00
Remove all event merging and dispatch logic from GFileMonitor. The only implementation of GFileMonitor outside of glib is in gvfs and it already does these things properly. Get rid of GLocalDirectoryMonitor. We will use a single class, GLocalFileMonitor, for both directory and file monitoring. This will prevent every single backend from having to create two objects separately (eg: ginotifydirectorymonitor.c and ginotifyfilemonitor.c). Introduce GFileMonitorSource as a thread-safe cross-context dispatch mechanism. Put it in GLocalFileMonitor. All backends will be expected to dispatch via the source and not touch the GFileMonitor object at all from the worker thread. Remove all construct properties from GLocalFileMonitor and remove the "context" construct property from GFileMonitor. All backends must now get the information about what file to monitor from the ->start() call which is mandatory to implement. Remove the implementation of rate limiting in GFileMonitor and add an implementation in GLocalFileMonitor. gvfs never did anything with this anyway, but if it wanted to, it would have to implement it for itself. This was done in order to get the rate_limit field into the GFileMonitorSource so that it could be safely accessed from the worker thread. Expose g_local_file_is_remote() internally for NFS detection. With the "is_remote" functionality exposed, we can now move all functions for creating local file monitors to a proper location in glocalfilemonitor.c Port the inotify backend to adjust to the changes above. None of the other backends are ported yet. Those will come in future commits.
587 lines
14 KiB
C
587 lines
14 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 void ip_event_callback (ik_event_t *event);
|
|
|
|
|
|
static void (*event_callback)(ik_event_t *event, inotify_sub *sub, gboolean file_event);
|
|
|
|
gboolean
|
|
_ip_startup (void (*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 void
|
|
ip_event_dispatch (GList *dir_list,
|
|
GList *file_list,
|
|
ik_event_t *event)
|
|
{
|
|
GList *l;
|
|
|
|
if (!event)
|
|
return;
|
|
|
|
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))
|
|
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
|
|
*/
|
|
|
|
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;
|
|
|
|
event_callback (event, sub, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
ip_event_callback (ik_event_t *event)
|
|
{
|
|
GList* dir_list = NULL;
|
|
GList *file_list = NULL;
|
|
|
|
/* We can ignore the IGNORED events */
|
|
if (event->mask & IN_IGNORED)
|
|
{
|
|
_ik_event_free (event);
|
|
return;
|
|
}
|
|
|
|
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)
|
|
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)
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|