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 static GHashTable *subs_hash_table = NULL;
47 G_LOCK_DEFINE_STATIC (hash_lock);
49 static int kqueue_descriptor = -1;
50 static int kqueue_socket_pair[] = {-1, -1};
51 static pthread_t kqueue_thread;
54 void _kh_file_appeared_cb (kqueue_sub *sub);
57 * accessor function for kqueue_descriptor
60 get_kqueue_descriptor()
62 return kqueue_descriptor;
66 * convert_kqueue_events_to_gio:
67 * @flags: a set of kqueue filter flags
68 * @done: a pointer to #gboolean indicating that the
69 * conversion has been done (out)
71 * Translates kqueue filter flags into GIO event flags.
73 * Returns: a #GFileMonitorEvent
75 static GFileMonitorEvent
76 convert_kqueue_events_to_gio (uint32_t flags, gboolean *done)
78 g_assert (done != NULL);
81 /* TODO: The following notifications should be emulated, if possible:
82 * - G_FILE_MONITOR_EVENT_PRE_UNMOUNT
84 if (flags & NOTE_DELETE)
87 return G_FILE_MONITOR_EVENT_DELETED;
89 if (flags & NOTE_ATTRIB)
92 return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
94 if (flags & (NOTE_WRITE | NOTE_EXTEND))
97 return G_FILE_MONITOR_EVENT_CHANGED;
99 if (flags & NOTE_RENAME)
102 return G_FILE_MONITOR_EVENT_MOVED;
104 if (flags & NOTE_REVOKE)
107 return G_FILE_MONITOR_EVENT_UNMOUNTED;
116 GFileMonitor *monitor;
121 * @udata: a pointer to user data (#handle_context).
122 * @path: file name of a new file.
123 * @inode: inode number of a new file.
125 * A callback function for the directory diff calculation routine,
126 * produces G_FILE_MONITOR_EVENT_CREATED event for a created file.
129 handle_created (void *udata, const char *path, ino_t inode)
131 handle_ctx *ctx = NULL;
136 ctx = (handle_ctx *) udata;
137 g_assert (udata != NULL);
138 g_assert (ctx->sub != NULL);
139 g_assert (ctx->monitor != NULL);
141 fpath = _ku_path_concat (ctx->sub->filename, path);
144 KH_W ("Failed to allocate a string for a new event");
148 file = g_file_new_for_path (fpath);
149 g_file_monitor_emit_event (ctx->monitor,
152 G_FILE_MONITOR_EVENT_CREATED);
154 g_object_unref (file);
159 * @udata: a pointer to user data (#handle_context).
160 * @path: file name of the removed file.
161 * @inode: inode number of the removed file.
163 * A callback function for the directory diff calculation routine,
164 * produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file.
167 handle_deleted (void *udata, const char *path, ino_t inode)
169 handle_ctx *ctx = NULL;
174 ctx = (handle_ctx *) udata;
175 g_assert (udata != NULL);
176 g_assert (ctx->sub != NULL);
177 g_assert (ctx->monitor != NULL);
179 fpath = _ku_path_concat (ctx->sub->filename, path);
182 KH_W ("Failed to allocate a string for a new event");
186 file = g_file_new_for_path (fpath);
187 g_file_monitor_emit_event (ctx->monitor,
190 G_FILE_MONITOR_EVENT_DELETED);
192 g_object_unref (file);
197 * @udata: a pointer to user data (#handle_context).
198 * @from_path: file name of the source file.
199 * @from_inode: inode number of the source file.
200 * @to_path: file name of the replaced file.
201 * @to_inode: inode number of the replaced file.
203 * A callback function for the directory diff calculation routine,
204 * produces G_FILE_MONITOR_EVENT_MOVED event on a move.
207 handle_moved (void *udata,
208 const char *from_path,
213 handle_ctx *ctx = NULL;
222 ctx = (handle_ctx *) udata;
223 g_assert (udata != NULL);
224 g_assert (ctx->sub != NULL);
225 g_assert (ctx->monitor != NULL);
228 path = _ku_path_concat (ctx->sub->filename, from_path);
229 npath = _ku_path_concat (ctx->sub->filename, to_path);
230 if (path == NULL || npath == NULL)
232 KH_W ("Failed to allocate strings for event");
236 file = g_file_new_for_path (path);
237 other = g_file_new_for_path (npath);
239 if (ctx->sub->pair_moves)
241 g_file_monitor_emit_event (ctx->monitor,
244 G_FILE_MONITOR_EVENT_MOVED);
248 g_file_monitor_emit_event (ctx->monitor,
251 G_FILE_MONITOR_EVENT_DELETED);
252 g_file_monitor_emit_event (ctx->monitor,
255 G_FILE_MONITOR_EVENT_CREATED);
261 g_object_unref (file);
262 g_object_unref (other);
267 * handle_overwritten:
268 * @data: a pointer to user data (#handle_context).
269 * @path: file name of the overwritten file.
270 * @node: inode number of the overwritten file.
272 * A callback function for the directory diff calculation routine,
273 * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when
274 * an overwrite occurs in the directory (see dep-list for details).
277 handle_overwritten (void *udata, const char *path, ino_t inode)
279 handle_ctx *ctx = NULL;
284 ctx = (handle_ctx *) udata;
285 g_assert (udata != NULL);
286 g_assert (ctx->sub != NULL);
287 g_assert (ctx->monitor != NULL);
289 fpath = _ku_path_concat (ctx->sub->filename, path);
292 KH_W ("Failed to allocate a string for a new event");
296 file = g_file_new_for_path (fpath);
297 g_file_monitor_emit_event (ctx->monitor,
300 G_FILE_MONITOR_EVENT_DELETED);
301 g_file_monitor_emit_event (ctx->monitor,
304 G_FILE_MONITOR_EVENT_CREATED);
307 g_object_unref (file);
310 static const traverse_cbs cbs = {
316 NULL, /* many added */
317 NULL, /* many removed */
318 NULL, /* names updated */
323 _kh_dir_diff (kqueue_sub *sub, GFileMonitor *monitor)
328 g_assert (sub != NULL);
329 g_assert (monitor != NULL);
331 memset (&ctx, 0, sizeof (handle_ctx));
333 ctx.monitor = monitor;
336 sub->deps = dl_listing (sub->filename);
338 dl_calculate (was, sub->deps, &cbs, &ctx);
345 * process_kqueue_notifications:
350 * Processes notifications, coming from the kqueue thread.
352 * Reads notifications from the command file descriptor, emits the
353 * "changed" event on the appropriate monitor.
355 * A typical GIO Channel callback function.
360 process_kqueue_notifications (GIOChannel *gioc,
364 struct kqueue_notification n;
365 kqueue_sub *sub = NULL;
366 GFileMonitor *monitor = NULL;
367 GFileMonitorEvent mask = 0;
369 g_assert (kqueue_socket_pair[0] != -1);
370 if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification)))
372 KH_W ("Failed to read a kqueue notification, error %d", errno);
377 sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd));
378 G_UNLOCK (hash_lock);
382 KH_W ("Got a notification for a deleted or non-existing subscription %d",
387 monitor = G_FILE_MONITOR (sub->user_data);
388 g_assert (monitor != NULL);
390 if (n.flags & (NOTE_DELETE | NOTE_REVOKE))
397 _km_add_missing (sub);
399 if (!(n.flags & NOTE_REVOKE))
401 /* Note that NOTE_REVOKE is issued by the kqueue thread
402 * on EV_ERROR kevent. In this case, a file descriptor is
403 * already closed from the kqueue thread, no need to close
405 _kh_cancel_sub (sub);
409 if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND))
411 _kh_dir_diff (sub, monitor);
412 n.flags &= ~(NOTE_WRITE | NOTE_EXTEND);
417 gboolean done = FALSE;
418 mask = convert_kqueue_events_to_gio (n.flags, &done);
421 GFile *file = g_file_new_for_path (sub->filename);
422 g_file_monitor_emit_event (monitor, file, NULL, mask);
423 g_object_unref (file);
435 * Kqueue backend startup code. Should be called only once.
437 * Returns: %TRUE on success, %FALSE otherwise.
440 _kh_startup_impl (gpointer unused)
442 GIOChannel *channel = NULL;
443 gboolean result = FALSE;
445 kqueue_descriptor = kqueue ();
446 result = (kqueue_descriptor != -1);
449 KH_W ("Failed to initialize kqueue\n!");
450 return GINT_TO_POINTER (FALSE);
453 result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair);
456 KH_W ("Failed to create socket pair\n!");
457 return GINT_TO_POINTER (FALSE) ;
460 result = pthread_create (&kqueue_thread,
463 &kqueue_socket_pair[1]);
466 KH_W ("Failed to run kqueue thread\n!");
467 return GINT_TO_POINTER (FALSE);
470 _km_init (_kh_file_appeared_cb);
472 channel = g_io_channel_unix_new (kqueue_socket_pair[0]);
473 g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL);
475 subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
477 KH_W ("started gio kqueue backend\n");
478 return GINT_TO_POINTER (TRUE);
484 * Kqueue backend initialization.
486 * Returns: %TRUE on success, %FALSE otherwise.
491 static GOnce init_once = G_ONCE_INIT;
492 g_once (&init_once, _kh_startup_impl, NULL);
493 return GPOINTER_TO_INT (init_once.retval);
498 * _kh_start_watching:
499 * @sub: a #kqueue_sub
501 * Starts watching on a subscription.
503 * Returns: %TRUE on success, %FALSE otherwise.
506 _kh_start_watching (kqueue_sub *sub)
508 g_assert (kqueue_socket_pair[0] != -1);
509 g_assert (sub != NULL);
510 g_assert (sub->filename != NULL);
512 /* kqueue requires a file descriptor to monitor. Sad but true */
513 #if defined (O_EVTONLY)
514 sub->fd = open (sub->filename, O_EVTONLY);
516 sub->fd = open (sub->filename, O_RDONLY);
521 KH_W ("failed to open file %s (error %d)", sub->filename, errno);
525 _ku_file_information (sub->fd, &sub->is_dir, NULL);
528 /* I know, it is very bad to make such decisions in this way and here.
529 * We already do have an user_data at the #kqueue_sub, and it may point to
530 * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case,
531 * we need to scan in contents for the further diffs. Ideally this process
532 * should be delegated to the GKqueueDirectoryMonitor, but for now I will
533 * do it in a dirty way right here. */
537 sub->deps = dl_listing (sub->filename);
541 g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub);
542 G_UNLOCK (hash_lock);
544 _kqueue_thread_push_fd (sub->fd);
546 /* Bump the kqueue thread. It will pick up a new sub entry to monitor */
547 if (!_ku_write (kqueue_socket_pair[0], "A", 1))
548 KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno);
555 * @sub: a #kqueue_sub
557 * Adds a subscription for monitoring.
559 * This funciton tries to start watching a subscription with
560 * _kh_start_watching(). On failure, i.e. when a file does not exist yet,
561 * the subscription will be added to a list of missing files to continue
562 * watching when the file will appear.
567 _kh_add_sub (kqueue_sub *sub)
569 g_assert (sub != NULL);
571 if (!_kh_start_watching (sub))
572 _km_add_missing (sub);
582 * Stops monitoring on a subscription.
587 _kh_cancel_sub (kqueue_sub *sub)
589 gboolean missing = FALSE;
590 g_assert (kqueue_socket_pair[0] != -1);
591 g_assert (sub != NULL);
594 missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd));
595 G_UNLOCK (hash_lock);
599 /* If there were no fd for this subscription, file is still
601 KH_W ("Removing subscription from missing");
606 /* fd will be closed in the kqueue thread */
607 _kqueue_thread_remove_fd (sub->fd);
609 /* Bump the kqueue thread. It will pick up a new sub entry to remove*/
610 if (!_ku_write (kqueue_socket_pair[0], "R", 1))
611 KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno);
619 * _kh_file_appeared_cb:
620 * @sub: a #kqueue_sub
622 * A callback function for kqueue-missing subsystem.
624 * Signals that a missing file has finally appeared in the filesystem.
625 * Emits %G_FILE_MONITOR_EVENT_CREATED.
628 _kh_file_appeared_cb (kqueue_sub *sub)
632 g_assert (sub != NULL);
633 g_assert (sub->filename);
635 if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
638 child = g_file_new_for_path (sub->filename);
640 g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
643 G_FILE_MONITOR_EVENT_CREATED);
645 g_object_unref (child);