/** * _kh_cancel_sub: * @sub a #kqueue_sub * * Stops monitoring on a subscription. * * Returns: %TRUE **/ gboolean _kh_cancel_sub (kqueue_sub *sub) { gboolean missing = FALSE; g_assert (kqueue_socket_pair[0] != -1); g_assert (sub != NULL); G_LOCK (hash_lock); missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd)); G_UNLOCK (hash_lock); if (missing) { /* If there were no fd for this subscription, file is still * missing. */ KH_W ("Removing subscription from missing"); _km_remove (sub); } else { /* fd will be closed in the kqueue thread */ _kqueue_thread_remove_fd (sub->fd); /* Bump the kqueue thread. It will pick up a new sub entry to remove*/ if (!_ku_write (kqueue_socket_pair[0], "R", 1)) KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno); } return TRUE; }
/** * _kh_cancel_sub: * @sub a #kqueue_sub * * Stops monitoring on a subscription. * * Returns: %TRUE **/ gboolean _kh_cancel_sub (kqueue_sub *sub) { gboolean removed = FALSE; g_assert (kqueue_socket_pair[0] != -1); g_assert (sub != NULL); _km_remove (sub); G_LOCK (hash_lock); removed = g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd)); G_UNLOCK (hash_lock); if (removed) { /* fd will be closed in the kqueue thread */ _kqueue_thread_remove_fd (sub->fd); /* Bump the kqueue thread. It will pick up a new sub entry to remove*/ if (!_ku_write (kqueue_socket_pair[0], "R", 1)) KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno); } return TRUE; }
/** * handle_overwritten: * @data: a pointer to user data (#handle_context). * @path: file name of the overwritten file. * @node: inode number of the overwritten file. * * A callback function for the directory diff calculation routine, * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when * an overwrite occurs in the directory (see dep-list for details). **/ static void handle_overwritten (void *udata, const char *path, ino_t inode) { handle_ctx *ctx = NULL; GFile *file = NULL; gchar *fpath = NULL; (void) inode; ctx = (handle_ctx *) udata; g_assert (udata != NULL); g_assert (ctx->sub != NULL); g_assert (ctx->monitor != NULL); fpath = _ku_path_concat (ctx->sub->filename, path); if (fpath == NULL) { KH_W ("Failed to allocate a string for a new event"); return; } file = g_file_new_for_path (fpath); g_file_monitor_emit_event (ctx->monitor, file, NULL, G_FILE_MONITOR_EVENT_DELETED); g_file_monitor_emit_event (ctx->monitor, file, NULL, G_FILE_MONITOR_EVENT_CREATED); g_free (fpath); g_object_unref (file); }
/** * _kh_start_watching: * @sub: a #kqueue_sub * * Starts watching on a subscription. * * Returns: %TRUE on success, %FALSE otherwise. **/ gboolean _kh_start_watching (kqueue_sub *sub) { g_assert (kqueue_socket_pair[0] != -1); g_assert (sub != NULL); g_assert (sub->filename != NULL); /* kqueue requires a file descriptor to monitor. Sad but true */ #if defined (O_EVTONLY) sub->fd = open (sub->filename, O_EVTONLY); #else sub->fd = open (sub->filename, O_RDONLY); #endif if (sub->fd == -1) { KH_W ("failed to open file %s (error %d)", sub->filename, errno); return FALSE; } _ku_file_information (sub->fd, &sub->is_dir, NULL); if (sub->is_dir) { /* I know, it is very bad to make such decisions in this way and here. * We already do have an user_data at the #kqueue_sub, and it may point to * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case, * we need to scan in contents for the further diffs. Ideally this process * should be delegated to the GKqueueDirectoryMonitor, but for now I will * do it in a dirty way right here. */ if (sub->deps) dl_free (sub->deps); sub->deps = dl_listing (sub->filename); } G_LOCK (hash_lock); g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub); G_UNLOCK (hash_lock); _kqueue_thread_push_fd (sub->fd); /* Bump the kqueue thread. It will pick up a new sub entry to monitor */ if (!_ku_write (kqueue_socket_pair[0], "A", 1)) KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno); return TRUE; }
/** * handle_moved: * @udata: a pointer to user data (#handle_context). * @from_path: file name of the source file. * @from_inode: inode number of the source file. * @to_path: file name of the replaced file. * @to_inode: inode number of the replaced file. * * A callback function for the directory diff calculation routine, * produces G_FILE_MONITOR_EVENT_MOVED event on a move. **/ static void handle_moved (void *udata, const char *from_path, ino_t from_inode, const char *to_path, ino_t to_inode) { handle_ctx *ctx = NULL; GFile *file = NULL; GFile *other = NULL; gchar *path = NULL; gchar *npath = NULL; (void) from_inode; (void) to_inode; ctx = (handle_ctx *) udata; g_assert (udata != NULL); g_assert (ctx->sub != NULL); g_assert (ctx->monitor != NULL); path = _ku_path_concat (ctx->sub->filename, from_path); npath = _ku_path_concat (ctx->sub->filename, to_path); if (path == NULL || npath == NULL) { KH_W ("Failed to allocate strings for event"); return; } file = g_file_new_for_path (path); other = g_file_new_for_path (npath); if (ctx->sub->pair_moves) { g_file_monitor_emit_event (ctx->monitor, file, other, G_FILE_MONITOR_EVENT_MOVED); } else { g_file_monitor_emit_event (ctx->monitor, file, NULL, G_FILE_MONITOR_EVENT_DELETED); g_file_monitor_emit_event (ctx->monitor, other, NULL, G_FILE_MONITOR_EVENT_CREATED); } g_free (path); g_free (npath); g_object_unref (file); g_object_unref (other); }
/* * _kh_startup_impl: * @unused: unused * * Kqueue backend startup code. Should be called only once. * * Returns: %TRUE on success, %FALSE otherwise. **/ static gpointer _kh_startup_impl (gpointer unused) { GIOChannel *channel = NULL; gboolean result = FALSE; kqueue_descriptor = kqueue (); result = (kqueue_descriptor != -1); if (!result) { KH_W ("Failed to initialize kqueue\n!"); return GINT_TO_POINTER (FALSE); } result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair); if (result != 0) { KH_W ("Failed to create socket pair\n!"); return GINT_TO_POINTER (FALSE) ; } result = pthread_create (&kqueue_thread, NULL, _kqueue_thread_func, &kqueue_socket_pair[1]); if (result != 0) { KH_W ("Failed to run kqueue thread\n!"); return GINT_TO_POINTER (FALSE); } _km_init (_kh_file_appeared_cb); channel = g_io_channel_unix_new (kqueue_socket_pair[0]); g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL); subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal); KH_W ("started gio kqueue backend\n"); return GINT_TO_POINTER (TRUE); }
/** * process_kqueue_notifications: * @gioc: unused. * @cond: unused. * @data: unused. * * Processes notifications, coming from the kqueue thread. * * Reads notifications from the command file descriptor, emits the * "changed" event on the appropriate monitor. * * A typical GIO Channel callback function. * * Returns: %TRUE **/ static gboolean process_kqueue_notifications (GIOChannel *gioc, GIOCondition cond, gpointer data) { struct kqueue_notification n; kqueue_sub *sub = NULL; GFileMonitorSource *source = NULL; GFileMonitorEvent mask = 0; g_assert (kqueue_socket_pair[0] != -1); if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification))) { KH_W ("Failed to read a kqueue notification, error %d", errno); return TRUE; } G_LOCK (hash_lock); sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd)); G_UNLOCK (hash_lock); if (sub == NULL) { KH_W ("Got a notification for a deleted or non-existing subscription %d", n.fd); return TRUE; } source = sub->user_data; g_assert (source != NULL); if (n.flags & (NOTE_DELETE | NOTE_REVOKE)) { if (sub->deps) { dl_free (sub->deps); sub->deps = NULL; } _km_add_missing (sub); if (!(n.flags & NOTE_REVOKE)) { /* Note that NOTE_REVOKE is issued by the kqueue thread * on EV_ERROR kevent. In this case, a file descriptor is * already closed from the kqueue thread, no need to close * it manually */ _kh_cancel_sub (sub); } } if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND)) { _kh_dir_diff (sub, source); n.flags &= ~(NOTE_WRITE | NOTE_EXTEND); } if (n.flags) { gboolean done = FALSE; mask = convert_kqueue_events_to_gio (n.flags, &done); if (done == TRUE) g_file_monitor_source_handle_event (source, mask, NULL, NULL, NULL, g_get_monotonic_time ()); } return TRUE; }