diff options
Diffstat (limited to 'gio')
-rw-r--r-- | gio/kqueue/gkqueuefilemonitor.c | 286 | ||||
-rw-r--r-- | gio/kqueue/kqueue-helper.c | 21 | ||||
-rw-r--r-- | gio/kqueue/kqueue-helper.h | 13 | ||||
-rw-r--r-- | gio/kqueue/kqueue-missing.c | 39 |
4 files changed, 284 insertions, 75 deletions
diff --git a/gio/kqueue/gkqueuefilemonitor.c b/gio/kqueue/gkqueuefilemonitor.c index d6fea41cf..816647512 100644 --- a/gio/kqueue/gkqueuefilemonitor.c +++ b/gio/kqueue/gkqueuefilemonitor.c @@ -33,6 +33,7 @@ #include <string.h> #include <glib-object.h> +#include <glib/gfileutils.h> #include <gio/gfilemonitor.h> #include <gio/glocalfilemonitor.h> #include <gio/giomodule.h> @@ -52,19 +53,44 @@ static int kq_queue = -1; #define G_KQUEUE_FILE_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor)) +/* C11 allows type redefinition, but GLib is configured to use C89, which causes + * clang to show warnings when we use a C11 feature. Since the C89 requirement + * is mostly used to support MSVC, we simply ignore the warning here because + * this file is never going to be useful on Windows. */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtypedef-redefinition" +#endif + typedef GLocalFileMonitorClass GKqueueFileMonitorClass; -typedef struct +/* When the file we are monitoring is a directory, sub_dir is subscribed to the + * directory itself and sub_file is NULL. + * + * When the file we are monitoring is a regular file, sub_dir is subscribed to + * the directory containing the file and sub_file is subscribed to the file + * being monitored. We have to monitor both because it is possible that the + * file chosen for monitoring doesn't exist when the file monitor is started. + * We monitor on its parent in order to get notification when it is created. + * + * To distinguish between a directory monitor and a regular file monitor, check + * whether sub_file is NULL. */ +typedef struct _GKqueueFileMonitor { GLocalFileMonitor parent_instance; - kqueue_sub *sub; + kqueue_sub *sub_dir; + kqueue_sub *sub_file; #ifndef O_EVTONLY GFileMonitor *fallback; GFile *fbfile; #endif } GKqueueFileMonitor; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + GType g_kqueue_file_monitor_get_type (void); G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR, g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME, @@ -78,12 +104,23 @@ G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL #define O_KQFLAG O_EVTONLY #endif -#define NOTE_ALL (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND|NOTE_ATTRIB|NOTE_RENAME) +static inline unsigned int +note_all (void) +{ + unsigned int notes = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME; +#ifdef NOTE_TRUNCATE + notes |= NOTE_TRUNCATE; +#endif +#ifdef NOTE_CLOSE_WRITE + notes |= NOTE_CLOSE_WRITE; +#endif + return notes; +} static gboolean g_kqueue_file_monitor_cancel (GFileMonitor* monitor); static gboolean g_kqueue_file_monitor_is_supported (void); -static kqueue_sub *_kqsub_new (const gchar *, GLocalFileMonitor *, GFileMonitorSource *); +static kqueue_sub *_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *); static void _kqsub_free (kqueue_sub *); static gboolean _kqsub_cancel (kqueue_sub *); @@ -138,11 +175,18 @@ g_kqueue_file_monitor_finalize (GObject *object) { GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object); - if (kqueue_monitor->sub) + if (kqueue_monitor->sub_dir) + { + _kqsub_cancel (kqueue_monitor->sub_dir); + _kqsub_free (kqueue_monitor->sub_dir); + kqueue_monitor->sub_dir = NULL; + } + + if (kqueue_monitor->sub_file) { - _kqsub_cancel (kqueue_monitor->sub); - _kqsub_free (kqueue_monitor->sub); - kqueue_monitor->sub = NULL; + _kqsub_cancel (kqueue_monitor->sub_file); + _kqsub_free (kqueue_monitor->sub_file); + kqueue_monitor->sub_file = NULL; } #ifndef O_EVTONLY @@ -165,17 +209,51 @@ g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor, GFileMonitorSource *source) { GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor); - kqueue_sub *sub; - const gchar *path; - - path = filename; - if (path == NULL) - path = dirname; + kqueue_sub *sub_dir = NULL, *sub_file = NULL; + gchar *path_dir, *path_file, *file_basename; + + /* There are three possible cases here: + * + * 1. Directory: dirname != NULL, basename == NULL, filename == NULL + * 2. Regular file: dirname != NULL, basename != NULL, filename == NULL + * 3. Hard links: dirname == NULL, basename == NULL, filename != NULL + * + * Note that we don't distinguish between case 2 and 3. Kqueue monitors + * files based on file descriptors, so we always receive events come from + * hard links. + */ + if (filename != NULL) + { + path_dir = g_path_get_dirname (filename); + path_file = g_strdup (filename); + file_basename = g_path_get_basename (filename); + } + else + { + path_dir = g_strdup (dirname); + if (basename != NULL) + { + path_file = g_build_filename (dirname, basename, NULL); + file_basename = g_strdup (basename); + } + else + { + path_file = NULL; + file_basename = NULL; + } + } #ifndef O_EVTONLY - if (_ke_is_excluded (path)) + if (_ke_is_excluded (path_dir)) { - GFile *file = g_file_new_for_path (path); + GFile *file; + if (path_file != NULL) + file = g_file_new_for_path (path_file); + else + file = g_file_new_for_path (path_dir); + g_free (path_dir); + g_free (path_file); + g_free (file_basename); kqueue_monitor->fbfile = file; kqueue_monitor->fallback = _g_poll_file_monitor_new (file); g_signal_connect (kqueue_monitor->fallback, "changed", @@ -191,13 +269,30 @@ g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor, * file, GIO uses a GKqueueFileMonitor object for that. If a directory * will be created under that path, GKqueueFileMonitor will have to * handle the directory notifications. */ - sub = _kqsub_new (path, local_monitor, source); - if (sub == NULL) - return; + sub_dir = _kqsub_new (g_steal_pointer (&path_dir), NULL, + kqueue_monitor, source); + if (!_kqsub_start_watching (sub_dir)) + _km_add_missing (sub_dir); + + /* Unlike GInotifyFileMonitor, which always uses a directory monitor + * regardless of the type of the file being monitored, kqueue doesn't + * give us events generated by files under it when we are monitoring + * a directory. We have to monitor the file itself to know changes which + * was made to the file itself. */ + if (path_file != NULL) + { + sub_file = _kqsub_new (g_steal_pointer (&path_file), + g_steal_pointer (&file_basename), + kqueue_monitor, source); + if (!_kqsub_start_watching (sub_file)) + _km_add_missing (sub_file); + } - kqueue_monitor->sub = sub; - if (!_kqsub_start_watching (sub)) - _km_add_missing (sub); + kqueue_monitor->sub_dir = sub_dir; + kqueue_monitor->sub_file = sub_file; + g_clear_pointer (&path_dir, g_free); + g_clear_pointer (&path_file, g_free); + g_clear_pointer (&file_basename, g_free); } static void @@ -230,59 +325,127 @@ g_kqueue_file_monitor_callback (gint fd, GIOCondition condition, gpointer user_d struct timespec ts; memset (&ts, 0, sizeof(ts)); + + /* We must hold the global lock before accessing any kqueue_sub because it is + * possible for other threads to call g_kqueue_file_monitor_cancel, which may + * free the kqueue_sub struct we are accessing. */ + G_LOCK (kq_lock); + while (kevent(fd, NULL, 0, &ev, 1, &ts) > 0) { - GFileMonitorEvent mask = 0; - if (ev.filter != EVFILT_VNODE || ev.udata == NULL) continue; - sub = ev.udata; + sub = ev.udata; source = sub->source; + /* When we are monitoring a regular file which already exists, ignore + * events generated by its parent directory. This has to be the first + * check to prevent the following code to emit useless events */ + if (sub->is_dir && sub->mon->sub_file != NULL && sub->mon->sub_file->fd != -1) + continue; + if (ev.flags & EV_ERROR) ev.fflags = NOTE_REVOKE; - if (ev.fflags & (NOTE_DELETE | NOTE_REVOKE)) - { - _kqsub_cancel (sub); - _km_add_missing (sub); - } - if (sub->is_dir && ev.fflags & (NOTE_WRITE | NOTE_EXTEND)) { - _kh_dir_diff (sub); + /* If we are monitoring on a non-existent regular file, trigger the + * rescan of missing files immediately so we don't have to wait for + * 4 seconds for discovering missing files. We pass the sub_file + * corresponding to the GKqueueFileMonitor to 'check_this_sub_only' + * argument to prevent _km_scan_missing from emiting 'CREATED' + * events because _kh_dir_diff will do it for us. */ + if (sub->mon->sub_file != NULL && sub->mon->sub_file->fd == -1) + _km_scan_missing (sub->mon->sub_file); + + /* If we are monitoring a regular file, don't emit 'DELETED' events + * from the directory monitor because it will be emitted from the + * file itself when a NOTE_DELETE is reported on sub_file. */ + _kh_dir_diff (sub, sub->mon->sub_file == NULL); + +#ifdef NOTE_TRUNCATE + ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE); +#else ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND); +#endif } + /* Here starts the long section of mapping kqueue events to + * GFileMonitorEvent. Since kqueue can return multiple events in a + * single kevent struct, we must use 'if' instead of 'else if'. */ if (ev.fflags & NOTE_DELETE) { - mask = G_FILE_MONITOR_EVENT_DELETED; + struct stat st; + if (fstat (sub->fd, &st) < 0) + st.st_nlink = 0; + + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_DELETED, + sub->basename, NULL, NULL, now); + + /* If the last reference to the file was removed, delete the + * subscription from kqueue and add it to the missing list. + * If you are monitoring a file which has hard link count higher + * than 1, it is possible for the same file to emit 'DELETED' + * events multiple times. */ + if (st.st_nlink == 0) + { + _kqsub_cancel (sub); + _km_add_missing (sub); + } } - else if (ev.fflags & NOTE_ATTRIB) + if (ev.fflags & NOTE_REVOKE) + { + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_UNMOUNTED, + sub->basename, NULL, NULL, now); + _kqsub_cancel (sub); + _km_add_missing (sub); + } + if (ev.fflags & NOTE_ATTRIB) { - mask = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED; + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, + sub->basename, NULL, NULL, now); } - else if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND)) +#ifdef NOTE_TRUNCATE + if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE)) +#else + if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND)) +#endif { - mask = G_FILE_MONITOR_EVENT_CHANGED; + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_CHANGED, + sub->basename, NULL, NULL, now); } - else if (ev.fflags & NOTE_RENAME) + if (ev.fflags & NOTE_RENAME) { /* Since there’s apparently no way to get the new name of the * file out of kqueue(), all we can do is say that this one has * been deleted. */ - mask = G_FILE_MONITOR_EVENT_DELETED; + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_DELETED, + sub->basename, NULL, NULL, now); } - else if (ev.fflags & NOTE_REVOKE) +#ifdef NOTE_CLOSE_WRITE + if (ev.fflags & NOTE_CLOSE_WRITE) { - mask = G_FILE_MONITOR_EVENT_UNMOUNTED; + g_file_monitor_source_handle_event (source, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, + sub->basename, NULL, NULL, now); } +#endif - if (mask) - g_file_monitor_source_handle_event (source, mask, NULL, NULL, NULL, now); + /* Handle the case when a file is created again shortly after it was + * deleted. It has to be the last check because 'DELETED' must happen + * before 'CREATED'. */ + if (ev.fflags & (NOTE_DELETE | NOTE_REVOKE)) + _km_scan_missing (NULL); } + G_UNLOCK (kq_lock); + return TRUE; } @@ -320,14 +483,28 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor) { GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor); - if (kqueue_monitor->sub) + /* We must hold the global lock before calling _kqsub_cancel. However, we + * cannot call G_LOCK in _kqsub_cancel because it is also used by + * g_kqueue_file_monitor_callback, which already holds the lock itself. */ + G_LOCK (kq_lock); + + if (kqueue_monitor->sub_dir) { - _kqsub_cancel (kqueue_monitor->sub); - _kqsub_free (kqueue_monitor->sub); - kqueue_monitor->sub = NULL; + _kqsub_cancel (kqueue_monitor->sub_dir); + _kqsub_free (kqueue_monitor->sub_dir); + kqueue_monitor->sub_dir = NULL; } + if (kqueue_monitor->sub_file) + { + _kqsub_cancel (kqueue_monitor->sub_file); + _kqsub_free (kqueue_monitor->sub_file); + kqueue_monitor->sub_file = NULL; + } + + G_UNLOCK (kq_lock); + #ifndef O_EVTONLY - else if (kqueue_monitor->fallback) + if (kqueue_monitor->fallback) { g_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor); g_file_monitor_cancel (kqueue_monitor->fallback); @@ -341,12 +518,13 @@ g_kqueue_file_monitor_cancel (GFileMonitor *monitor) } static kqueue_sub * -_kqsub_new (const gchar *filename, GLocalFileMonitor *mon, GFileMonitorSource *source) +_kqsub_new (gchar *filename, gchar *basename, GKqueueFileMonitor *mon, GFileMonitorSource *source) { kqueue_sub *sub; sub = g_slice_new (kqueue_sub); - sub->filename = g_strdup (filename); + sub->filename = filename; + sub->basename = basename; sub->mon = mon; g_source_ref ((GSource *) source); sub->source = source; @@ -365,19 +543,23 @@ _kqsub_free (kqueue_sub *sub) g_source_unref ((GSource *) sub->source); g_free (sub->filename); + g_free (sub->basename); g_slice_free (kqueue_sub, sub); } static gboolean _kqsub_cancel (kqueue_sub *sub) { + /* WARNING: Before calling this function, you must hold a lock on kq_lock + * or you will cause use-after-free in g_kqueue_file_monitor_callback. */ + struct kevent ev; /* Remove the event and close the file descriptor to automatically * delete pending events. */ if (sub->fd != -1) { - EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_DELETE, NOTE_ALL, 0, sub); + EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_DELETE, note_all (), 0, sub); if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1) { g_warning ("Unable to remove event for %s: %s", sub->filename, g_strerror (errno)); @@ -425,7 +607,7 @@ _kqsub_start_watching (kqueue_sub *sub) sub->deps = dl_listing (sub->filename); } - EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ALL, 0, sub); + EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note_all (), 0, sub); if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1) { g_warning ("Unable to add event for %s: %s", sub->filename, g_strerror (errno)); diff --git a/gio/kqueue/kqueue-helper.c b/gio/kqueue/kqueue-helper.c index 497c30b15..36a5b58a7 100644 --- a/gio/kqueue/kqueue-helper.c +++ b/gio/kqueue/kqueue-helper.c @@ -25,6 +25,7 @@ #include <sys/event.h> #include <sys/time.h> #include <sys/socket.h> +#include <sys/stat.h> #include <gio/glocalfile.h> #include <gio/glocalfilemonitor.h> #include <gio/gfile.h> @@ -38,6 +39,7 @@ typedef struct { kqueue_sub *sub; GFileMonitorSource *source; + gboolean handle_deleted; } handle_ctx; /** @@ -53,6 +55,9 @@ static void handle_created (void *udata, const char *path, ino_t inode) { handle_ctx *ctx = NULL; + gint64 now; + gchar *fullname; + struct stat st; (void) inode; ctx = (handle_ctx *) udata; @@ -60,8 +65,16 @@ handle_created (void *udata, const char *path, ino_t inode) g_assert (ctx->sub != NULL); g_assert (ctx->source != NULL); + now = g_get_monotonic_time (); g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED, path, - NULL, NULL, g_get_monotonic_time ()); + NULL, NULL, now); + + /* Copied from ih_event_callback to report 'CHANGES_DONE_HINT' earlier. */ + fullname = g_build_filename (ctx->sub->filename, path, NULL); + if (stat (fullname, &st) != 0 || !S_ISREG (st.st_mode) || st.st_nlink != 1) + g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, path, + NULL, NULL, now); + g_free (fullname); } /** @@ -84,6 +97,9 @@ handle_deleted (void *udata, const char *path, ino_t inode) g_assert (ctx->sub != NULL); g_assert (ctx->source != NULL); + if (!ctx->handle_deleted) + return; + g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, path, NULL, NULL, g_get_monotonic_time ()); } @@ -161,7 +177,7 @@ static const traverse_cbs cbs = { void -_kh_dir_diff (kqueue_sub *sub) +_kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted) { dep_list *was; handle_ctx ctx; @@ -169,6 +185,7 @@ _kh_dir_diff (kqueue_sub *sub) memset (&ctx, 0, sizeof (handle_ctx)); ctx.sub = sub; ctx.source = sub->source; + ctx.handle_deleted = handle_deleted; was = sub->deps; sub->deps = dl_listing (sub->filename); diff --git a/gio/kqueue/kqueue-helper.h b/gio/kqueue/kqueue-helper.h index 38a32a2f9..418b38c08 100644 --- a/gio/kqueue/kqueue-helper.h +++ b/gio/kqueue/kqueue-helper.h @@ -28,26 +28,33 @@ #include "dep-list.h" +typedef struct _GKqueueFileMonitor GKqueueFileMonitor; + /** * kqueue_sub: + * @mon: a pointer to the GKqueueFileMonitor which holds this subscription * @filename: a name of the file to monitor * @fd: the associated file descriptor (used by kqueue) * - * Represents a subscription on a file or directory. + * Represents a subscription on a file or directory. To check whether a + * subscription is active, check the fd field. If fd is not -1, it is an + * active subscription which can emit events from kqueue. */ typedef struct { - GLocalFileMonitor *mon; + GKqueueFileMonitor *mon; GFileMonitorSource *source; gchar* filename; + gchar* basename; int fd; dep_list* deps; int is_dir; } kqueue_sub; gboolean _kqsub_start_watching (kqueue_sub *sub); -void _kh_dir_diff (kqueue_sub *sub); +void _kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted); void _km_add_missing (kqueue_sub *sub); +gboolean _km_scan_missing (kqueue_sub *check_this_sub_only); void _km_remove (kqueue_sub *sub); #endif /* __KQUEUE_HELPER_H */ diff --git a/gio/kqueue/kqueue-missing.c b/gio/kqueue/kqueue-missing.c index 93135b962..a08f3a736 100644 --- a/gio/kqueue/kqueue-missing.c +++ b/gio/kqueue/kqueue-missing.c @@ -21,16 +21,13 @@ *******************************************************************************/ #include <glib.h> +#include "glib-private.h" #include "kqueue-helper.h" #define SCAN_MISSING_TIME 4 /* 1/4 Hz */ -void _kh_file_appeared_cb (kqueue_sub *sub); - -static gboolean km_scan_missing (gpointer user_data); - static gboolean km_debug_enabled = FALSE; #define KM_W if (km_debug_enabled) g_warning @@ -40,6 +37,12 @@ G_LOCK_DEFINE_STATIC (missing_lock); static volatile gboolean scan_missing_running = FALSE; +static gboolean +_km_scan_missing_cb (gpointer user_data) +{ + return _km_scan_missing (NULL); +} + /** * _km_add_missing: * @sub: a #kqueue_sub @@ -77,10 +80,10 @@ _km_add_missing (kqueue_sub *sub) * Signals that a missing file has finally appeared in the filesystem. * Emits %G_FILE_MONITOR_EVENT_CREATED. **/ -void +static void _kh_file_appeared_cb (kqueue_sub *sub) { - GFile *child; + gint64 now = g_get_monotonic_time (); g_assert (sub != NULL); g_assert (sub->filename); @@ -88,18 +91,14 @@ _kh_file_appeared_cb (kqueue_sub *sub) if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS)) return; - child = g_file_new_for_path (sub->filename); - - g_file_monitor_emit_event (G_FILE_MONITOR (sub->mon), - child, - NULL, - G_FILE_MONITOR_EVENT_CREATED); - - g_object_unref (child); + g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CREATED, + sub->basename, NULL, NULL, now); + g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, + sub->basename, NULL, NULL, now); } /** - * km_scan_missing: + * _km_scan_missing: * @user_data: unused * * The core missing files watching routine. @@ -110,8 +109,8 @@ _kh_file_appeared_cb (kqueue_sub *sub) * * Returns: %FALSE if no missing files left, %TRUE otherwise. **/ -static gboolean -km_scan_missing (gpointer user_data) +gboolean +_km_scan_missing (kqueue_sub *check_this_sub_only) { GSList *head; GSList *not_missing = NULL; @@ -128,10 +127,14 @@ km_scan_missing (gpointer user_data) g_assert (sub != NULL); g_assert (sub->filename != NULL); + if (check_this_sub_only != NULL && sub != check_this_sub_only) + continue; + if (_kqsub_start_watching (sub)) { KM_W ("file %s now exists, starting watching", sub->filename); - _kh_file_appeared_cb (sub); + if (check_this_sub_only == NULL) + _kh_file_appeared_cb (sub); not_missing = g_slist_prepend (not_missing, head); } } |