nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /******************************************************************************* |
2 | Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk> |
||
3 | |||
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: |
||
10 | |||
11 | The above copyright notice and this permission notice shall be included in |
||
12 | all copies or substantial portions of the Software. |
||
13 | |||
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 |
||
20 | THE SOFTWARE. |
||
21 | *******************************************************************************/ |
||
22 | |||
23 | #include "config.h" |
||
24 | #include <sys/types.h> |
||
25 | #include <sys/event.h> |
||
26 | #include <sys/time.h> |
||
27 | #include <sys/socket.h> |
||
28 | #include <gio/glocalfile.h> |
||
29 | #include <gio/glocalfilemonitor.h> |
||
30 | #include <gio/gfile.h> |
||
31 | #include <fcntl.h> |
||
32 | #include <unistd.h> |
||
33 | #include <string.h> |
||
34 | #include <errno.h> |
||
35 | #include <pthread.h> |
||
36 | #include "kqueue-helper.h" |
||
37 | #include "kqueue-utils.h" |
||
38 | #include "kqueue-thread.h" |
||
39 | #include "kqueue-missing.h" |
||
40 | #include "kqueue-exclusions.h" |
||
41 | |||
42 | static gboolean kh_debug_enabled = FALSE; |
||
43 | #define KH_W if (kh_debug_enabled) g_warning |
||
44 | |||
45 | static GHashTable *subs_hash_table = NULL; |
||
46 | G_LOCK_DEFINE_STATIC (hash_lock); |
||
47 | |||
48 | static int kqueue_descriptor = -1; |
||
49 | static int kqueue_socket_pair[] = {-1, -1}; |
||
50 | static pthread_t kqueue_thread; |
||
51 | |||
52 | |||
53 | void _kh_file_appeared_cb (kqueue_sub *sub); |
||
54 | |||
55 | /** |
||
56 | * accessor function for kqueue_descriptor |
||
57 | **/ |
||
58 | int |
||
59 | get_kqueue_descriptor() |
||
60 | { |
||
61 | return kqueue_descriptor; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * convert_kqueue_events_to_gio: |
||
66 | * @flags: a set of kqueue filter flags |
||
67 | * @done: a pointer to #gboolean indicating that the |
||
68 | * conversion has been done (out) |
||
69 | * |
||
70 | * Translates kqueue filter flags into GIO event flags. |
||
71 | * |
||
72 | * Returns: a #GFileMonitorEvent |
||
73 | **/ |
||
74 | static GFileMonitorEvent |
||
75 | convert_kqueue_events_to_gio (uint32_t flags, gboolean *done) |
||
76 | { |
||
77 | g_assert (done != NULL); |
||
78 | *done = FALSE; |
||
79 | |||
80 | /* TODO: The following notifications should be emulated, if possible: |
||
81 | * - G_FILE_MONITOR_EVENT_PRE_UNMOUNT |
||
82 | */ |
||
83 | if (flags & NOTE_DELETE) |
||
84 | { |
||
85 | *done = TRUE; |
||
86 | return G_FILE_MONITOR_EVENT_DELETED; |
||
87 | } |
||
88 | if (flags & NOTE_ATTRIB) |
||
89 | { |
||
90 | *done = TRUE; |
||
91 | return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED; |
||
92 | } |
||
93 | if (flags & (NOTE_WRITE | NOTE_EXTEND)) |
||
94 | { |
||
95 | *done = TRUE; |
||
96 | return G_FILE_MONITOR_EVENT_CHANGED; |
||
97 | } |
||
98 | if (flags & NOTE_RENAME) |
||
99 | { |
||
100 | *done = TRUE; |
||
101 | return G_FILE_MONITOR_EVENT_MOVED; |
||
102 | } |
||
103 | if (flags & NOTE_REVOKE) |
||
104 | { |
||
105 | *done = TRUE; |
||
106 | return G_FILE_MONITOR_EVENT_UNMOUNTED; |
||
107 | } |
||
108 | |||
109 | /* done is FALSE */ |
||
110 | return 0; |
||
111 | } |
||
112 | |||
113 | typedef struct { |
||
114 | kqueue_sub *sub; |
||
115 | GFileMonitorSource *source; |
||
116 | } handle_ctx; |
||
117 | |||
118 | /** |
||
119 | * handle_created: |
||
120 | * @udata: a pointer to user data (#handle_context). |
||
121 | * @path: file name of a new file. |
||
122 | * @inode: inode number of a new file. |
||
123 | * |
||
124 | * A callback function for the directory diff calculation routine, |
||
125 | * produces G_FILE_MONITOR_EVENT_CREATED event for a created file. |
||
126 | **/ |
||
127 | static void |
||
128 | handle_created (void *udata, const char *path, ino_t inode) |
||
129 | { |
||
130 | handle_ctx *ctx = NULL; |
||
131 | |||
132 | (void) inode; |
||
133 | ctx = (handle_ctx *) udata; |
||
134 | g_assert (udata != NULL); |
||
135 | g_assert (ctx->sub != NULL); |
||
136 | g_assert (ctx->source != NULL); |
||
137 | |||
138 | g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED, path, |
||
139 | NULL, NULL, g_get_monotonic_time ()); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * handle_deleted: |
||
144 | * @udata: a pointer to user data (#handle_context). |
||
145 | * @path: file name of the removed file. |
||
146 | * @inode: inode number of the removed file. |
||
147 | * |
||
148 | * A callback function for the directory diff calculation routine, |
||
149 | * produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file. |
||
150 | **/ |
||
151 | static void |
||
152 | handle_deleted (void *udata, const char *path, ino_t inode) |
||
153 | { |
||
154 | handle_ctx *ctx = NULL; |
||
155 | |||
156 | (void) inode; |
||
157 | ctx = (handle_ctx *) udata; |
||
158 | g_assert (udata != NULL); |
||
159 | g_assert (ctx->sub != NULL); |
||
160 | g_assert (ctx->source != NULL); |
||
161 | |||
162 | g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, path, |
||
163 | NULL, NULL, g_get_monotonic_time ()); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * handle_moved: |
||
168 | * @udata: a pointer to user data (#handle_context). |
||
169 | * @from_path: file name of the source file. |
||
170 | * @from_inode: inode number of the source file. |
||
171 | * @to_path: file name of the replaced file. |
||
172 | * @to_inode: inode number of the replaced file. |
||
173 | * |
||
174 | * A callback function for the directory diff calculation routine, |
||
175 | * produces G_FILE_MONITOR_EVENT_RENAMED event on a move. |
||
176 | **/ |
||
177 | static void |
||
178 | handle_moved (void *udata, |
||
179 | const char *from_path, |
||
180 | ino_t from_inode, |
||
181 | const char *to_path, |
||
182 | ino_t to_inode) |
||
183 | { |
||
184 | handle_ctx *ctx = NULL; |
||
185 | |||
186 | (void) from_inode; |
||
187 | (void) to_inode; |
||
188 | |||
189 | ctx = (handle_ctx *) udata; |
||
190 | g_assert (udata != NULL); |
||
191 | g_assert (ctx->sub != NULL); |
||
192 | g_assert (ctx->source != NULL); |
||
193 | |||
194 | g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_RENAMED, |
||
195 | from_path, to_path, NULL, g_get_monotonic_time ()); |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * handle_overwritten: |
||
200 | * @data: a pointer to user data (#handle_context). |
||
201 | * @path: file name of the overwritten file. |
||
202 | * @node: inode number of the overwritten file. |
||
203 | * |
||
204 | * A callback function for the directory diff calculation routine, |
||
205 | * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when |
||
206 | * an overwrite occurs in the directory (see dep-list for details). |
||
207 | **/ |
||
208 | static void |
||
209 | handle_overwritten (void *udata, const char *path, ino_t inode) |
||
210 | { |
||
211 | handle_ctx *ctx = NULL; |
||
212 | |||
213 | (void) inode; |
||
214 | ctx = (handle_ctx *) udata; |
||
215 | g_assert (udata != NULL); |
||
216 | g_assert (ctx->sub != NULL); |
||
217 | g_assert (ctx->source != NULL); |
||
218 | |||
219 | g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, |
||
220 | path, NULL, NULL, g_get_monotonic_time ()); |
||
221 | |||
222 | g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED, |
||
223 | path, NULL, NULL, g_get_monotonic_time ()); |
||
224 | } |
||
225 | |||
226 | static const traverse_cbs cbs = { |
||
227 | handle_created, |
||
228 | handle_deleted, |
||
229 | handle_moved, |
||
230 | handle_overwritten, |
||
231 | handle_moved, |
||
232 | NULL, /* many added */ |
||
233 | NULL, /* many removed */ |
||
234 | NULL, /* names updated */ |
||
235 | }; |
||
236 | |||
237 | |||
238 | void |
||
239 | _kh_dir_diff (kqueue_sub *sub, GFileMonitorSource *source) |
||
240 | { |
||
241 | dep_list *was; |
||
242 | handle_ctx ctx; |
||
243 | |||
244 | g_assert (sub != NULL); |
||
245 | g_assert (source != NULL); |
||
246 | |||
247 | memset (&ctx, 0, sizeof (handle_ctx)); |
||
248 | ctx.sub = sub; |
||
249 | ctx.source = source; |
||
250 | |||
251 | was = sub->deps; |
||
252 | sub->deps = dl_listing (sub->filename); |
||
253 | |||
254 | dl_calculate (was, sub->deps, &cbs, &ctx); |
||
255 | |||
256 | dl_free (was); |
||
257 | } |
||
258 | |||
259 | |||
260 | /** |
||
261 | * process_kqueue_notifications: |
||
262 | * @gioc: unused. |
||
263 | * @cond: unused. |
||
264 | * @data: unused. |
||
265 | * |
||
266 | * Processes notifications, coming from the kqueue thread. |
||
267 | * |
||
268 | * Reads notifications from the command file descriptor, emits the |
||
269 | * "changed" event on the appropriate monitor. |
||
270 | * |
||
271 | * A typical GIO Channel callback function. |
||
272 | * |
||
273 | * Returns: %TRUE |
||
274 | **/ |
||
275 | static gboolean |
||
276 | process_kqueue_notifications (GIOChannel *gioc, |
||
277 | GIOCondition cond, |
||
278 | gpointer data) |
||
279 | { |
||
280 | struct kqueue_notification n; |
||
281 | kqueue_sub *sub = NULL; |
||
282 | GFileMonitorSource *source = NULL; |
||
283 | GFileMonitorEvent mask = 0; |
||
284 | |||
285 | g_assert (kqueue_socket_pair[0] != -1); |
||
286 | if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification))) |
||
287 | { |
||
288 | KH_W ("Failed to read a kqueue notification, error %d", errno); |
||
289 | return TRUE; |
||
290 | } |
||
291 | |||
292 | G_LOCK (hash_lock); |
||
293 | sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd)); |
||
294 | G_UNLOCK (hash_lock); |
||
295 | |||
296 | if (sub == NULL) |
||
297 | { |
||
298 | KH_W ("Got a notification for a deleted or non-existing subscription %d", |
||
299 | n.fd); |
||
300 | return TRUE; |
||
301 | } |
||
302 | |||
303 | source = sub->user_data; |
||
304 | g_assert (source != NULL); |
||
305 | |||
306 | if (n.flags & (NOTE_DELETE | NOTE_REVOKE)) |
||
307 | { |
||
308 | if (sub->deps) |
||
309 | { |
||
310 | dl_free (sub->deps); |
||
311 | sub->deps = NULL; |
||
312 | } |
||
313 | _km_add_missing (sub); |
||
314 | |||
315 | if (!(n.flags & NOTE_REVOKE)) |
||
316 | { |
||
317 | /* Note that NOTE_REVOKE is issued by the kqueue thread |
||
318 | * on EV_ERROR kevent. In this case, a file descriptor is |
||
319 | * already closed from the kqueue thread, no need to close |
||
320 | * it manually */ |
||
321 | _kh_cancel_sub (sub); |
||
322 | } |
||
323 | } |
||
324 | |||
325 | if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND)) |
||
326 | { |
||
327 | _kh_dir_diff (sub, source); |
||
328 | n.flags &= ~(NOTE_WRITE | NOTE_EXTEND); |
||
329 | } |
||
330 | |||
331 | if (n.flags) |
||
332 | { |
||
333 | gboolean done = FALSE; |
||
334 | mask = convert_kqueue_events_to_gio (n.flags, &done); |
||
335 | if (done == TRUE) |
||
336 | g_file_monitor_source_handle_event (source, mask, NULL, NULL, NULL, g_get_monotonic_time ()); |
||
337 | } |
||
338 | |||
339 | return TRUE; |
||
340 | } |
||
341 | |||
342 | |||
343 | /* |
||
344 | * _kh_startup_impl: |
||
345 | * @unused: unused |
||
346 | * |
||
347 | * Kqueue backend startup code. Should be called only once. |
||
348 | * |
||
349 | * Returns: %TRUE on success, %FALSE otherwise. |
||
350 | **/ |
||
351 | static gpointer |
||
352 | _kh_startup_impl (gpointer unused) |
||
353 | { |
||
354 | GIOChannel *channel = NULL; |
||
355 | gboolean result = FALSE; |
||
356 | |||
357 | kqueue_descriptor = kqueue (); |
||
358 | result = (kqueue_descriptor != -1); |
||
359 | if (!result) |
||
360 | { |
||
361 | KH_W ("Failed to initialize kqueue\n!"); |
||
362 | return GINT_TO_POINTER (FALSE); |
||
363 | } |
||
364 | |||
365 | result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair); |
||
366 | if (result != 0) |
||
367 | { |
||
368 | KH_W ("Failed to create socket pair\n!"); |
||
369 | return GINT_TO_POINTER (FALSE) ; |
||
370 | } |
||
371 | |||
372 | result = pthread_create (&kqueue_thread, |
||
373 | NULL, |
||
374 | _kqueue_thread_func, |
||
375 | &kqueue_socket_pair[1]); |
||
376 | if (result != 0) |
||
377 | { |
||
378 | KH_W ("Failed to run kqueue thread\n!"); |
||
379 | return GINT_TO_POINTER (FALSE); |
||
380 | } |
||
381 | |||
382 | _km_init (_kh_file_appeared_cb); |
||
383 | |||
384 | channel = g_io_channel_unix_new (kqueue_socket_pair[0]); |
||
385 | g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL); |
||
386 | |||
387 | subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal); |
||
388 | |||
389 | KH_W ("started gio kqueue backend\n"); |
||
390 | return GINT_TO_POINTER (TRUE); |
||
391 | } |
||
392 | |||
393 | |||
394 | /* |
||
395 | * _kh_startup: |
||
396 | * Kqueue backend initialization. |
||
397 | * |
||
398 | * Returns: %TRUE on success, %FALSE otherwise. |
||
399 | **/ |
||
400 | gboolean |
||
401 | _kh_startup (void) |
||
402 | { |
||
403 | static GOnce init_once = G_ONCE_INIT; |
||
404 | g_once (&init_once, _kh_startup_impl, NULL); |
||
405 | return GPOINTER_TO_INT (init_once.retval); |
||
406 | } |
||
407 | |||
408 | |||
409 | /** |
||
410 | * _kh_start_watching: |
||
411 | * @sub: a #kqueue_sub |
||
412 | * |
||
413 | * Starts watching on a subscription. |
||
414 | * |
||
415 | * Returns: %TRUE on success, %FALSE otherwise. |
||
416 | **/ |
||
417 | gboolean |
||
418 | _kh_start_watching (kqueue_sub *sub) |
||
419 | { |
||
420 | g_assert (kqueue_socket_pair[0] != -1); |
||
421 | g_assert (sub != NULL); |
||
422 | g_assert (sub->filename != NULL); |
||
423 | |||
424 | /* kqueue requires a file descriptor to monitor. Sad but true */ |
||
425 | #if defined (O_EVTONLY) |
||
426 | sub->fd = open (sub->filename, O_EVTONLY); |
||
427 | #else |
||
428 | sub->fd = open (sub->filename, O_RDONLY); |
||
429 | #endif |
||
430 | |||
431 | if (sub->fd == -1) |
||
432 | { |
||
433 | KH_W ("failed to open file %s (error %d)", sub->filename, errno); |
||
434 | return FALSE; |
||
435 | } |
||
436 | |||
437 | _ku_file_information (sub->fd, &sub->is_dir, NULL); |
||
438 | if (sub->is_dir) |
||
439 | { |
||
440 | /* I know, it is very bad to make such decisions in this way and here. |
||
441 | * We already do have an user_data at the #kqueue_sub, and it may point to |
||
442 | * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case, |
||
443 | * we need to scan in contents for the further diffs. Ideally this process |
||
444 | * should be delegated to the GKqueueDirectoryMonitor, but for now I will |
||
445 | * do it in a dirty way right here. */ |
||
446 | if (sub->deps) |
||
447 | dl_free (sub->deps); |
||
448 | |||
449 | sub->deps = dl_listing (sub->filename); |
||
450 | } |
||
451 | |||
452 | G_LOCK (hash_lock); |
||
453 | g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub); |
||
454 | G_UNLOCK (hash_lock); |
||
455 | |||
456 | _kqueue_thread_push_fd (sub->fd); |
||
457 | |||
458 | /* Bump the kqueue thread. It will pick up a new sub entry to monitor */ |
||
459 | if (!_ku_write (kqueue_socket_pair[0], "A", 1)) |
||
460 | KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno); |
||
461 | return TRUE; |
||
462 | } |
||
463 | |||
464 | |||
465 | /** |
||
466 | * _kh_add_sub: |
||
467 | * @sub: a #kqueue_sub |
||
468 | * |
||
469 | * Adds a subscription for monitoring. |
||
470 | * |
||
471 | * This funciton tries to start watching a subscription with |
||
472 | * _kh_start_watching(). On failure, i.e. when a file does not exist yet, |
||
473 | * the subscription will be added to a list of missing files to continue |
||
474 | * watching when the file will appear. |
||
475 | * |
||
476 | * Returns: %TRUE |
||
477 | **/ |
||
478 | gboolean |
||
479 | _kh_add_sub (kqueue_sub *sub) |
||
480 | { |
||
481 | g_assert (sub != NULL); |
||
482 | |||
483 | if (!_kh_start_watching (sub)) |
||
484 | _km_add_missing (sub); |
||
485 | |||
486 | return TRUE; |
||
487 | } |
||
488 | |||
489 | |||
490 | /** |
||
491 | * _kh_cancel_sub: |
||
492 | * @sub a #kqueue_sub |
||
493 | * |
||
494 | * Stops monitoring on a subscription. |
||
495 | * |
||
496 | * Returns: %TRUE |
||
497 | **/ |
||
498 | gboolean |
||
499 | _kh_cancel_sub (kqueue_sub *sub) |
||
500 | { |
||
501 | gboolean missing = FALSE; |
||
502 | g_assert (kqueue_socket_pair[0] != -1); |
||
503 | g_assert (sub != NULL); |
||
504 | |||
505 | G_LOCK (hash_lock); |
||
506 | missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd)); |
||
507 | G_UNLOCK (hash_lock); |
||
508 | |||
509 | if (missing) |
||
510 | { |
||
511 | /* If there were no fd for this subscription, file is still |
||
512 | * missing. */ |
||
513 | KH_W ("Removing subscription from missing"); |
||
514 | _km_remove (sub); |
||
515 | } |
||
516 | else |
||
517 | { |
||
518 | /* fd will be closed in the kqueue thread */ |
||
519 | _kqueue_thread_remove_fd (sub->fd); |
||
520 | |||
521 | /* Bump the kqueue thread. It will pick up a new sub entry to remove*/ |
||
522 | if (!_ku_write (kqueue_socket_pair[0], "R", 1)) |
||
523 | KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno); |
||
524 | } |
||
525 | |||
526 | return TRUE; |
||
527 | } |
||
528 | |||
529 | |||
530 | /** |
||
531 | * _kh_file_appeared_cb: |
||
532 | * @sub: a #kqueue_sub |
||
533 | * |
||
534 | * A callback function for kqueue-missing subsystem. |
||
535 | * |
||
536 | * Signals that a missing file has finally appeared in the filesystem. |
||
537 | * Emits %G_FILE_MONITOR_EVENT_CREATED. |
||
538 | **/ |
||
539 | void |
||
540 | _kh_file_appeared_cb (kqueue_sub *sub) |
||
541 | { |
||
542 | GFile* child; |
||
543 | |||
544 | g_assert (sub != NULL); |
||
545 | g_assert (sub->filename); |
||
546 | |||
547 | if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS)) |
||
548 | return; |
||
549 | |||
550 | child = g_file_new_for_path (sub->filename); |
||
551 | |||
552 | g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data), |
||
553 | child, |
||
554 | NULL, |
||
555 | G_FILE_MONITOR_EVENT_CREATED); |
||
556 | |||
557 | g_object_unref (child); |
||
558 | } |