1 /*******************************************************************************
2 Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 *******************************************************************************/
24 #include <sys/types.h>
25 #include <sys/event.h>
27 #include <sys/socket.h>
28 #include <gio/glocalfile.h>
29 #include <gio/gfile.h>
35 #include "kqueue-helper.h"
36 #include "kqueue-utils.h"
37 #include "kqueue-thread.h"
38 #include "kqueue-missing.h"
39 #include "kqueue-exclusions.h"
41 #include "gkqueuedirectorymonitor.h"
43 static gboolean kh_debug_enabled = FALSE;
44 #define KH_W if (kh_debug_enabled) g_warning
46 G_GNUC_INTERNAL G_LOCK_DEFINE (kqueue_lock);
48 static GHashTable *subs_hash_table = NULL;
49 G_GNUC_INTERNAL G_LOCK_DEFINE (hash_lock);
51 static int kqueue_descriptor = -1;
52 static int kqueue_socket_pair[] = {-1, -1};
53 static pthread_t kqueue_thread;
56 void _kh_file_appeared_cb (kqueue_sub *sub);
59 * accessor function for kqueue_descriptor
62 get_kqueue_descriptor()
64 return kqueue_descriptor;
68 * convert_kqueue_events_to_gio:
69 * @flags: a set of kqueue filter flags
70 * @done: a pointer to #gboolean indicating that the
71 * conversion has been done (out)
73 * Translates kqueue filter flags into GIO event flags.
75 * Returns: a #GFileMonitorEvent
77 static GFileMonitorEvent
78 convert_kqueue_events_to_gio (uint32_t flags, gboolean *done)
80 g_assert (done != NULL);
83 /* TODO: The following notifications should be emulated, if possible:
84 * - G_FILE_MONITOR_EVENT_PRE_UNMOUNT
86 if (flags & NOTE_DELETE)
89 return G_FILE_MONITOR_EVENT_DELETED;
91 if (flags & NOTE_ATTRIB)
94 return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
96 if (flags & (NOTE_WRITE | NOTE_EXTEND))
99 return G_FILE_MONITOR_EVENT_CHANGED;
101 if (flags & NOTE_RENAME)
104 return G_FILE_MONITOR_EVENT_MOVED;
106 if (flags & NOTE_REVOKE)
109 return G_FILE_MONITOR_EVENT_UNMOUNTED;
118 GFileMonitor *monitor;
123 * @udata: a pointer to user data (#handle_context).
124 * @path: file name of a new file.
125 * @inode: inode number of a new file.
127 * A callback function for the directory diff calculation routine,
128 * produces G_FILE_MONITOR_EVENT_CREATED event for a created file.
131 handle_created (void *udata, const char *path, ino_t inode)
133 handle_ctx *ctx = NULL;
138 ctx = (handle_ctx *) udata;
139 g_assert (udata != NULL);
140 g_assert (ctx->sub != NULL);
141 g_assert (ctx->monitor != NULL);
143 fpath = _ku_path_concat (ctx->sub->filename, path);
146 KH_W ("Failed to allocate a string for a new event");
150 file = g_file_new_for_path (fpath);
151 g_file_monitor_emit_event (ctx->monitor,
154 G_FILE_MONITOR_EVENT_CREATED);
156 g_object_unref (file);
161 * @udata: a pointer to user data (#handle_context).
162 * @path: file name of the removed file.
163 * @inode: inode number of the removed file.
165 * A callback function for the directory diff calculation routine,
166 * produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file.
169 handle_deleted (void *udata, const char *path, ino_t inode)
171 handle_ctx *ctx = NULL;
176 ctx = (handle_ctx *) udata;
177 g_assert (udata != NULL);
178 g_assert (ctx->sub != NULL);
179 g_assert (ctx->monitor != NULL);
181 fpath = _ku_path_concat (ctx->sub->filename, path);
184 KH_W ("Failed to allocate a string for a new event");
188 file = g_file_new_for_path (fpath);
189 g_file_monitor_emit_event (ctx->monitor,
192 G_FILE_MONITOR_EVENT_DELETED);
194 g_object_unref (file);
199 * @udata: a pointer to user data (#handle_context).
200 * @from_path: file name of the source file.
201 * @from_inode: inode number of the source file.
202 * @to_path: file name of the replaced file.
203 * @to_inode: inode number of the replaced file.
205 * A callback function for the directory diff calculation routine,
206 * produces G_FILE_MONITOR_EVENT_MOVED event on a move.
209 handle_moved (void *udata,
210 const char *from_path,
215 handle_ctx *ctx = NULL;
224 ctx = (handle_ctx *) udata;
225 g_assert (udata != NULL);
226 g_assert (ctx->sub != NULL);
227 g_assert (ctx->monitor != NULL);
230 path = _ku_path_concat (ctx->sub->filename, from_path);
231 npath = _ku_path_concat (ctx->sub->filename, to_path);
232 if (path == NULL || npath == NULL)
234 KH_W ("Failed to allocate strings for event");
238 file = g_file_new_for_path (path);
239 other = g_file_new_for_path (npath);
241 if (ctx->sub->pair_moves)
243 g_file_monitor_emit_event (ctx->monitor,
246 G_FILE_MONITOR_EVENT_MOVED);
250 g_file_monitor_emit_event (ctx->monitor,
253 G_FILE_MONITOR_EVENT_DELETED);
254 g_file_monitor_emit_event (ctx->monitor,
257 G_FILE_MONITOR_EVENT_CREATED);
263 g_object_unref (file);
264 g_object_unref (other);
269 * handle_overwritten:
270 * @data: a pointer to user data (#handle_context).
271 * @path: file name of the overwritten file.
272 * @node: inode number of the overwritten file.
274 * A callback function for the directory diff calculation routine,
275 * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when
276 * an overwrite occurs in the directory (see dep-list for details).
279 handle_overwritten (void *udata, const char *path, ino_t inode)
281 handle_ctx *ctx = NULL;
286 ctx = (handle_ctx *) udata;
287 g_assert (udata != NULL);
288 g_assert (ctx->sub != NULL);
289 g_assert (ctx->monitor != NULL);
291 fpath = _ku_path_concat (ctx->sub->filename, path);
294 KH_W ("Failed to allocate a string for a new event");
298 file = g_file_new_for_path (fpath);
299 g_file_monitor_emit_event (ctx->monitor,
302 G_FILE_MONITOR_EVENT_DELETED);
303 g_file_monitor_emit_event (ctx->monitor,
306 G_FILE_MONITOR_EVENT_CREATED);
309 g_object_unref (file);
312 static const traverse_cbs cbs = {
318 NULL, /* many added */
319 NULL, /* many removed */
320 NULL, /* names updated */
325 _kh_dir_diff (kqueue_sub *sub, GFileMonitor *monitor)
330 g_assert (sub != NULL);
331 g_assert (monitor != NULL);
333 memset (&ctx, 0, sizeof (handle_ctx));
335 ctx.monitor = monitor;
338 sub->deps = dl_listing (sub->filename);
340 dl_calculate (was, sub->deps, &cbs, &ctx);
347 * process_kqueue_notifications:
352 * Processes notifications, coming from the kqueue thread.
354 * Reads notifications from the command file descriptor, emits the
355 * "changed" event on the appropriate monitor.
357 * A typical GIO Channel callback function.
362 process_kqueue_notifications (GIOChannel *gioc,
366 struct kqueue_notification n;
367 kqueue_sub *sub = NULL;
368 GFileMonitor *monitor = NULL;
369 GFileMonitorEvent mask = 0;
371 g_assert (kqueue_socket_pair[0] != -1);
372 if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification)))
374 KH_W ("Failed to read a kqueue notification, error %d", errno);
379 sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd));
380 G_UNLOCK (hash_lock);
384 KH_W ("Got a notification for a deleted or non-existing subscription %d",
389 monitor = G_FILE_MONITOR (sub->user_data);
390 g_assert (monitor != NULL);
392 if (n.flags & (NOTE_DELETE | NOTE_REVOKE))
399 _km_add_missing (sub);
401 if (!(n.flags & NOTE_REVOKE))
403 /* Note that NOTE_REVOKE is issued by the kqueue thread
404 * on EV_ERROR kevent. In this case, a file descriptor is
405 * already closed from the kqueue thread, no need to close
407 _kh_cancel_sub (sub);
411 if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND))
413 _kh_dir_diff (sub, monitor);
414 n.flags &= ~(NOTE_WRITE | NOTE_EXTEND);
419 gboolean done = FALSE;
420 mask = convert_kqueue_events_to_gio (n.flags, &done);
423 GFile *file = g_file_new_for_path (sub->filename);
424 g_file_monitor_emit_event (monitor, file, NULL, mask);
425 g_object_unref (file);
437 * Kqueue backend startup code. Should be called only once.
439 * Returns: %TRUE on success, %FALSE otherwise.
442 _kh_startup_impl (gpointer unused)
444 GIOChannel *channel = NULL;
445 gboolean result = FALSE;
447 kqueue_descriptor = kqueue ();
448 result = (kqueue_descriptor != -1);
451 KH_W ("Failed to initialize kqueue\n!");
452 return GINT_TO_POINTER (FALSE);
455 result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair);
458 KH_W ("Failed to create socket pair\n!");
459 return GINT_TO_POINTER (FALSE) ;
462 result = pthread_create (&kqueue_thread,
465 &kqueue_socket_pair[1]);
468 KH_W ("Failed to run kqueue thread\n!");
469 return GINT_TO_POINTER (FALSE);
472 _km_init (_kh_file_appeared_cb);
474 channel = g_io_channel_unix_new (kqueue_socket_pair[0]);
475 g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL);
477 subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
479 KH_W ("started gio kqueue backend\n");
480 return GINT_TO_POINTER (TRUE);
486 * Kqueue backend initialization.
488 * Returns: %TRUE on success, %FALSE otherwise.
493 static GOnce init_once = G_ONCE_INIT;
494 g_once (&init_once, _kh_startup_impl, NULL);
495 return GPOINTER_TO_INT (init_once.retval);
500 * _kh_start_watching:
501 * @sub: a #kqueue_sub
503 * Starts watching on a subscription.
505 * Returns: %TRUE on success, %FALSE otherwise.
508 _kh_start_watching (kqueue_sub *sub)
510 g_assert (kqueue_socket_pair[0] != -1);
511 g_assert (sub != NULL);
512 g_assert (sub->filename != NULL);
514 /* kqueue requires a file descriptor to monitor. Sad but true */
515 #if defined (O_EVTONLY)
516 sub->fd = open (sub->filename, O_EVTONLY);
518 sub->fd = open (sub->filename, O_RDONLY);
523 KH_W ("failed to open file %s (error %d)", sub->filename, errno);
527 _ku_file_information (sub->fd, &sub->is_dir, NULL);
530 /* I know, it is very bad to make such decisions in this way and here.
531 * We already do have an user_data at the #kqueue_sub, and it may point to
532 * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case,
533 * we need to scan in contents for the further diffs. Ideally this process
534 * should be delegated to the GKqueueDirectoryMonitor, but for now I will
535 * do it in a dirty way right here. */
539 sub->deps = dl_listing (sub->filename);
543 g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub);
544 G_UNLOCK (hash_lock);
546 _kqueue_thread_push_fd (sub->fd);
548 /* Bump the kqueue thread. It will pick up a new sub entry to monitor */
549 if (!_ku_write (kqueue_socket_pair[0], "A", 1))
550 KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno);
557 * @sub: a #kqueue_sub
559 * Adds a subscription for monitoring.
561 * This funciton tries to start watching a subscription with
562 * _kh_start_watching(). On failure, i.e. when a file does not exist yet,
563 * the subscription will be added to a list of missing files to continue
564 * watching when the file will appear.
569 _kh_add_sub (kqueue_sub *sub)
571 g_assert (sub != NULL);
573 if (!_kh_start_watching (sub))
574 _km_add_missing (sub);
584 * Stops monitoring on a subscription.
589 _kh_cancel_sub (kqueue_sub *sub)
591 gboolean missing = FALSE;
592 g_assert (kqueue_socket_pair[0] != -1);
593 g_assert (sub != NULL);
596 missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd));
597 G_UNLOCK (hash_lock);
601 /* If there were no fd for this subscription, file is still
603 KH_W ("Removing subscription from missing");
608 /* fd will be closed in the kqueue thread */
609 _kqueue_thread_remove_fd (sub->fd);
611 /* Bump the kqueue thread. It will pick up a new sub entry to remove*/
612 if (!_ku_write (kqueue_socket_pair[0], "R", 1))
613 KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno);
621 * _kh_file_appeared_cb:
622 * @sub: a #kqueue_sub
624 * A callback function for kqueue-missing subsystem.
626 * Signals that a missing file has finally appeared in the filesystem.
627 * Emits %G_FILE_MONITOR_EVENT_CREATED.
630 _kh_file_appeared_cb (kqueue_sub *sub)
634 g_assert (sub != NULL);
635 g_assert (sub->filename);
637 if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
640 child = g_file_new_for_path (sub->filename);
642 g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
645 G_FILE_MONITOR_EVENT_CREATED);
647 g_object_unref (child);