|
160
|
1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
|
2 * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
3 * of this software and associated documentation files (the "Software"), to
|
|
|
4 * deal in the Software without restriction, including without limitation the
|
|
|
5 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
6 * sell copies of the Software, and to permit persons to whom the Software is
|
|
|
7 * furnished to do so, subject to the following conditions:
|
|
|
8 *
|
|
|
9 * The above copyright notice and this permission notice shall be included in
|
|
|
10 * all copies or substantial portions of the Software.
|
|
|
11 *
|
|
|
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
13 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
14 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
15 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
16 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
17 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
18 * IN THE SOFTWARE.
|
|
|
19 */
|
|
|
20
|
|
|
21 #include "uv.h"
|
|
|
22 #include "internal.h"
|
|
|
23
|
|
|
24 #if TARGET_OS_IPHONE || MAC_OS_X_VERSION_MAX_ALLOWED < 1070
|
|
|
25
|
|
|
26 /* iOS (currently) doesn't provide the FSEvents-API (nor CoreServices) */
|
|
|
27 /* macOS prior to 10.7 doesn't provide the full FSEvents API so use kqueue */
|
|
|
28
|
|
|
29 int uv__fsevents_init(uv_fs_event_t* handle) {
|
|
|
30 return 0;
|
|
|
31 }
|
|
|
32
|
|
|
33
|
|
|
34 int uv__fsevents_close(uv_fs_event_t* handle) {
|
|
|
35 return 0;
|
|
|
36 }
|
|
|
37
|
|
|
38
|
|
|
39 void uv__fsevents_loop_delete(uv_loop_t* loop) {
|
|
|
40 }
|
|
|
41
|
|
|
42 #else /* TARGET_OS_IPHONE */
|
|
|
43
|
|
|
44 #include "darwin-stub.h"
|
|
|
45
|
|
|
46 #include <dlfcn.h>
|
|
|
47 #include <assert.h>
|
|
|
48 #include <stdlib.h>
|
|
|
49 #include <pthread.h>
|
|
|
50
|
|
|
51 static const int kFSEventsModified =
|
|
|
52 kFSEventStreamEventFlagItemChangeOwner |
|
|
|
53 kFSEventStreamEventFlagItemFinderInfoMod |
|
|
|
54 kFSEventStreamEventFlagItemInodeMetaMod |
|
|
|
55 kFSEventStreamEventFlagItemModified |
|
|
|
56 kFSEventStreamEventFlagItemXattrMod;
|
|
|
57
|
|
|
58 static const int kFSEventsRenamed =
|
|
|
59 kFSEventStreamEventFlagItemCreated |
|
|
|
60 kFSEventStreamEventFlagItemRemoved |
|
|
|
61 kFSEventStreamEventFlagItemRenamed;
|
|
|
62
|
|
|
63 static const int kFSEventsSystem =
|
|
|
64 kFSEventStreamEventFlagUserDropped |
|
|
|
65 kFSEventStreamEventFlagKernelDropped |
|
|
|
66 kFSEventStreamEventFlagEventIdsWrapped |
|
|
|
67 kFSEventStreamEventFlagHistoryDone |
|
|
|
68 kFSEventStreamEventFlagMount |
|
|
|
69 kFSEventStreamEventFlagUnmount |
|
|
|
70 kFSEventStreamEventFlagRootChanged;
|
|
|
71
|
|
|
72 typedef struct uv__fsevents_event_s uv__fsevents_event_t;
|
|
|
73 typedef struct uv__cf_loop_signal_s uv__cf_loop_signal_t;
|
|
|
74 typedef struct uv__cf_loop_state_s uv__cf_loop_state_t;
|
|
|
75
|
|
|
76 enum uv__cf_loop_signal_type_e {
|
|
|
77 kUVCFLoopSignalRegular,
|
|
|
78 kUVCFLoopSignalClosing
|
|
|
79 };
|
|
|
80 typedef enum uv__cf_loop_signal_type_e uv__cf_loop_signal_type_t;
|
|
|
81
|
|
|
82 struct uv__cf_loop_signal_s {
|
|
|
83 struct uv__queue member;
|
|
|
84 uv_fs_event_t* handle;
|
|
|
85 uv__cf_loop_signal_type_t type;
|
|
|
86 };
|
|
|
87
|
|
|
88 struct uv__fsevents_event_s {
|
|
|
89 struct uv__queue member;
|
|
|
90 int events;
|
|
|
91 char path[1];
|
|
|
92 };
|
|
|
93
|
|
|
94 struct uv__cf_loop_state_s {
|
|
|
95 CFRunLoopRef loop;
|
|
|
96 CFRunLoopSourceRef signal_source;
|
|
|
97 int fsevent_need_reschedule;
|
|
|
98 FSEventStreamRef fsevent_stream;
|
|
|
99 uv_sem_t fsevent_sem;
|
|
|
100 uv_mutex_t fsevent_mutex;
|
|
|
101 struct uv__queue fsevent_handles;
|
|
|
102 unsigned int fsevent_handle_count;
|
|
|
103 };
|
|
|
104
|
|
|
105 /* Forward declarations */
|
|
|
106 static void uv__cf_loop_cb(void* arg);
|
|
|
107 static void* uv__cf_loop_runner(void* arg);
|
|
|
108 static int uv__cf_loop_signal(uv_loop_t* loop,
|
|
|
109 uv_fs_event_t* handle,
|
|
|
110 uv__cf_loop_signal_type_t type);
|
|
|
111
|
|
|
112 /* Lazy-loaded by uv__fsevents_global_init(). */
|
|
|
113 static CFArrayRef (*pCFArrayCreate)(CFAllocatorRef,
|
|
|
114 const void**,
|
|
|
115 CFIndex,
|
|
|
116 const CFArrayCallBacks*);
|
|
|
117 static void (*pCFRelease)(CFTypeRef);
|
|
|
118 static void (*pCFRunLoopAddSource)(CFRunLoopRef,
|
|
|
119 CFRunLoopSourceRef,
|
|
|
120 CFStringRef);
|
|
|
121 static CFRunLoopRef (*pCFRunLoopGetCurrent)(void);
|
|
|
122 static void (*pCFRunLoopRemoveSource)(CFRunLoopRef,
|
|
|
123 CFRunLoopSourceRef,
|
|
|
124 CFStringRef);
|
|
|
125 static void (*pCFRunLoopRun)(void);
|
|
|
126 static CFRunLoopSourceRef (*pCFRunLoopSourceCreate)(CFAllocatorRef,
|
|
|
127 CFIndex,
|
|
|
128 CFRunLoopSourceContext*);
|
|
|
129 static void (*pCFRunLoopSourceSignal)(CFRunLoopSourceRef);
|
|
|
130 static void (*pCFRunLoopStop)(CFRunLoopRef);
|
|
|
131 static void (*pCFRunLoopWakeUp)(CFRunLoopRef);
|
|
|
132 static CFStringRef (*pCFStringCreateWithFileSystemRepresentation)(
|
|
|
133 CFAllocatorRef,
|
|
|
134 const char*);
|
|
|
135 static CFStringRef (*pkCFRunLoopDefaultMode);
|
|
|
136 static FSEventStreamRef (*pFSEventStreamCreate)(CFAllocatorRef,
|
|
|
137 FSEventStreamCallback,
|
|
|
138 FSEventStreamContext*,
|
|
|
139 CFArrayRef,
|
|
|
140 FSEventStreamEventId,
|
|
|
141 CFTimeInterval,
|
|
|
142 FSEventStreamCreateFlags);
|
|
|
143 static void (*pFSEventStreamInvalidate)(FSEventStreamRef);
|
|
|
144 static void (*pFSEventStreamRelease)(FSEventStreamRef);
|
|
|
145 static void (*pFSEventStreamScheduleWithRunLoop)(FSEventStreamRef,
|
|
|
146 CFRunLoopRef,
|
|
|
147 CFStringRef);
|
|
|
148 static int (*pFSEventStreamStart)(FSEventStreamRef);
|
|
|
149 static void (*pFSEventStreamStop)(FSEventStreamRef);
|
|
|
150
|
|
|
151 #define UV__FSEVENTS_PROCESS(handle, block) \
|
|
|
152 do { \
|
|
|
153 struct uv__queue events; \
|
|
|
154 struct uv__queue* q; \
|
|
|
155 uv__fsevents_event_t* event; \
|
|
|
156 int err; \
|
|
|
157 uv_mutex_lock(&(handle)->cf_mutex); \
|
|
|
158 /* Split-off all events and empty original queue */ \
|
|
|
159 uv__queue_move(&(handle)->cf_events, &events); \
|
|
|
160 /* Get error (if any) and zero original one */ \
|
|
|
161 err = (handle)->cf_error; \
|
|
|
162 (handle)->cf_error = 0; \
|
|
|
163 uv_mutex_unlock(&(handle)->cf_mutex); \
|
|
|
164 /* Loop through events, deallocating each after processing */ \
|
|
|
165 while (!uv__queue_empty(&events)) { \
|
|
|
166 q = uv__queue_head(&events); \
|
|
|
167 event = uv__queue_data(q, uv__fsevents_event_t, member); \
|
|
|
168 uv__queue_remove(q); \
|
|
|
169 /* NOTE: Checking uv__is_active() is required here, because handle \
|
|
|
170 * callback may close handle and invoking it after it will lead to \
|
|
|
171 * incorrect behaviour */ \
|
|
|
172 if (!uv__is_closing((handle)) && uv__is_active((handle))) \
|
|
|
173 block \
|
|
|
174 /* Free allocated data */ \
|
|
|
175 uv__free(event); \
|
|
|
176 } \
|
|
|
177 if (err != 0 && !uv__is_closing((handle)) && uv__is_active((handle))) \
|
|
|
178 (handle)->cb((handle), NULL, 0, err); \
|
|
|
179 } while (0)
|
|
|
180
|
|
|
181
|
|
|
182 /* Runs in UV loop's thread, when there're events to report to handle */
|
|
|
183 static void uv__fsevents_cb(uv_async_t* cb) {
|
|
|
184 uv_fs_event_t* handle;
|
|
|
185
|
|
|
186 handle = cb->data;
|
|
|
187
|
|
|
188 UV__FSEVENTS_PROCESS(handle, {
|
|
|
189 handle->cb(handle, event->path[0] ? event->path : NULL, event->events, 0);
|
|
|
190 });
|
|
|
191 }
|
|
|
192
|
|
|
193
|
|
|
194 /* Runs in CF thread, pushed event into handle's event list */
|
|
|
195 static void uv__fsevents_push_event(uv_fs_event_t* handle,
|
|
|
196 struct uv__queue* events,
|
|
|
197 int err) {
|
|
|
198 assert(events != NULL || err != 0);
|
|
|
199 uv_mutex_lock(&handle->cf_mutex);
|
|
|
200
|
|
|
201 /* Concatenate two queues */
|
|
|
202 if (events != NULL)
|
|
|
203 uv__queue_add(&handle->cf_events, events);
|
|
|
204
|
|
|
205 /* Propagate error */
|
|
|
206 if (err != 0)
|
|
|
207 handle->cf_error = err;
|
|
|
208 uv_mutex_unlock(&handle->cf_mutex);
|
|
|
209
|
|
|
210 uv_async_send(handle->cf_cb);
|
|
|
211 }
|
|
|
212
|
|
|
213
|
|
|
214 /* Runs in CF thread, when there're events in FSEventStream */
|
|
|
215 static void uv__fsevents_event_cb(const FSEventStreamRef streamRef,
|
|
|
216 void* info,
|
|
|
217 size_t numEvents,
|
|
|
218 void* eventPaths,
|
|
|
219 const FSEventStreamEventFlags eventFlags[],
|
|
|
220 const FSEventStreamEventId eventIds[]) {
|
|
|
221 size_t i;
|
|
|
222 int len;
|
|
|
223 char** paths;
|
|
|
224 char* path;
|
|
|
225 char* pos;
|
|
|
226 uv_fs_event_t* handle;
|
|
|
227 struct uv__queue* q;
|
|
|
228 uv_loop_t* loop;
|
|
|
229 uv__cf_loop_state_t* state;
|
|
|
230 uv__fsevents_event_t* event;
|
|
|
231 FSEventStreamEventFlags flags;
|
|
|
232 struct uv__queue head;
|
|
|
233
|
|
|
234 loop = info;
|
|
|
235 state = loop->cf_state;
|
|
|
236 assert(state != NULL);
|
|
|
237 paths = eventPaths;
|
|
|
238
|
|
|
239 /* For each handle */
|
|
|
240 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
241 uv__queue_foreach(q, &state->fsevent_handles) {
|
|
|
242 handle = uv__queue_data(q, uv_fs_event_t, cf_member);
|
|
|
243 uv__queue_init(&head);
|
|
|
244
|
|
|
245 /* Process and filter out events */
|
|
|
246 for (i = 0; i < numEvents; i++) {
|
|
|
247 flags = eventFlags[i];
|
|
|
248
|
|
|
249 /* Ignore system events */
|
|
|
250 if (flags & kFSEventsSystem)
|
|
|
251 continue;
|
|
|
252
|
|
|
253 path = paths[i];
|
|
|
254 len = strlen(path);
|
|
|
255
|
|
|
256 if (handle->realpath_len == 0)
|
|
|
257 continue; /* This should be unreachable */
|
|
|
258
|
|
|
259 /* Filter out paths that are outside handle's request */
|
|
|
260 if (len < handle->realpath_len)
|
|
|
261 continue;
|
|
|
262
|
|
|
263 /* Make sure that realpath actually named a directory,
|
|
|
264 * (unless watching root, which alone keeps a trailing slash on the realpath)
|
|
|
265 * or that we matched the whole string */
|
|
|
266 if (handle->realpath_len != len &&
|
|
|
267 handle->realpath_len > 1 &&
|
|
|
268 path[handle->realpath_len] != '/')
|
|
|
269 continue;
|
|
|
270
|
|
|
271 if (memcmp(path, handle->realpath, handle->realpath_len) != 0)
|
|
|
272 continue;
|
|
|
273
|
|
|
274 if (!(handle->realpath_len == 1 && handle->realpath[0] == '/')) {
|
|
|
275 /* Remove common prefix, unless the watched folder is "/" */
|
|
|
276 path += handle->realpath_len;
|
|
|
277 len -= handle->realpath_len;
|
|
|
278
|
|
|
279 if (len == 0) {
|
|
|
280 /* Since we're using fsevents to watch the file itself,
|
|
|
281 * realpath == path, and we now need to get the basename of the file back
|
|
|
282 * (for commonality with other codepaths and platforms). */
|
|
|
283 while (len < handle->realpath_len && path[-1] != '/') {
|
|
|
284 path--;
|
|
|
285 len++;
|
|
|
286 }
|
|
|
287 /* Created and Removed seem to be always set, but don't make sense */
|
|
|
288 flags &= ~kFSEventsRenamed;
|
|
|
289 } else {
|
|
|
290 /* Skip forward slash */
|
|
|
291 path++;
|
|
|
292 len--;
|
|
|
293 }
|
|
|
294 }
|
|
|
295
|
|
|
296 /* Do not emit events from subdirectories (without option set) */
|
|
|
297 if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != '\0') {
|
|
|
298 pos = strchr(path + 1, '/');
|
|
|
299 if (pos != NULL)
|
|
|
300 continue;
|
|
|
301 }
|
|
|
302
|
|
|
303 event = uv__malloc(sizeof(*event) + len);
|
|
|
304 if (event == NULL)
|
|
|
305 break;
|
|
|
306
|
|
|
307 memset(event, 0, sizeof(*event));
|
|
|
308 memcpy(event->path, path, len + 1);
|
|
|
309 event->events = UV_RENAME;
|
|
|
310
|
|
|
311 if (0 == (flags & kFSEventsRenamed)) {
|
|
|
312 if (0 != (flags & kFSEventsModified) ||
|
|
|
313 0 == (flags & kFSEventStreamEventFlagItemIsDir))
|
|
|
314 event->events = UV_CHANGE;
|
|
|
315 }
|
|
|
316
|
|
|
317 uv__queue_insert_tail(&head, &event->member);
|
|
|
318 }
|
|
|
319
|
|
|
320 if (!uv__queue_empty(&head))
|
|
|
321 uv__fsevents_push_event(handle, &head, 0);
|
|
|
322 }
|
|
|
323 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
324 }
|
|
|
325
|
|
|
326
|
|
|
327 /* Runs in CF thread */
|
|
|
328 static int uv__fsevents_create_stream(uv__cf_loop_state_t* state,
|
|
|
329 uv_loop_t* loop,
|
|
|
330 CFArrayRef paths) {
|
|
|
331 FSEventStreamContext ctx;
|
|
|
332 FSEventStreamRef ref;
|
|
|
333 CFAbsoluteTime latency;
|
|
|
334 FSEventStreamCreateFlags flags;
|
|
|
335
|
|
|
336 /* Initialize context */
|
|
|
337 memset(&ctx, 0, sizeof(ctx));
|
|
|
338 ctx.info = loop;
|
|
|
339
|
|
|
340 latency = 0.05;
|
|
|
341
|
|
|
342 /* Explanation of selected flags:
|
|
|
343 * 1. NoDefer - without this flag, events that are happening continuously
|
|
|
344 * (i.e. each event is happening after time interval less than `latency`,
|
|
|
345 * counted from previous event), will be deferred and passed to callback
|
|
|
346 * once they'll either fill whole OS buffer, or when this continuous stream
|
|
|
347 * will stop (i.e. there'll be delay between events, bigger than
|
|
|
348 * `latency`).
|
|
|
349 * Specifying this flag will invoke callback after `latency` time passed
|
|
|
350 * since event.
|
|
|
351 * 2. FileEvents - fire callback for file changes too (by default it is firing
|
|
|
352 * it only for directory changes).
|
|
|
353 */
|
|
|
354 flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
|
|
|
355
|
|
|
356 /*
|
|
|
357 * NOTE: It might sound like a good idea to remember last seen StreamEventId,
|
|
|
358 * but in reality one dir might have last StreamEventId less than, the other,
|
|
|
359 * that is being watched now. Which will cause FSEventStream API to report
|
|
|
360 * changes to files from the past.
|
|
|
361 */
|
|
|
362 ref = pFSEventStreamCreate(NULL,
|
|
|
363 &uv__fsevents_event_cb,
|
|
|
364 &ctx,
|
|
|
365 paths,
|
|
|
366 kFSEventStreamEventIdSinceNow,
|
|
|
367 latency,
|
|
|
368 flags);
|
|
|
369 assert(ref != NULL);
|
|
|
370
|
|
|
371 pFSEventStreamScheduleWithRunLoop(ref, state->loop, *pkCFRunLoopDefaultMode);
|
|
|
372 if (!pFSEventStreamStart(ref)) {
|
|
|
373 pFSEventStreamInvalidate(ref);
|
|
|
374 pFSEventStreamRelease(ref);
|
|
|
375 return UV_EMFILE;
|
|
|
376 }
|
|
|
377
|
|
|
378 state->fsevent_stream = ref;
|
|
|
379 return 0;
|
|
|
380 }
|
|
|
381
|
|
|
382
|
|
|
383 /* Runs in CF thread */
|
|
|
384 static void uv__fsevents_destroy_stream(uv__cf_loop_state_t* state) {
|
|
|
385 if (state->fsevent_stream == NULL)
|
|
|
386 return;
|
|
|
387
|
|
|
388 /* Stop emitting events */
|
|
|
389 pFSEventStreamStop(state->fsevent_stream);
|
|
|
390
|
|
|
391 /* Release stream */
|
|
|
392 pFSEventStreamInvalidate(state->fsevent_stream);
|
|
|
393 pFSEventStreamRelease(state->fsevent_stream);
|
|
|
394 state->fsevent_stream = NULL;
|
|
|
395 }
|
|
|
396
|
|
|
397
|
|
|
398 /* Runs in CF thread, when there're new fsevent handles to add to stream */
|
|
|
399 static void uv__fsevents_reschedule(uv__cf_loop_state_t* state,
|
|
|
400 uv_loop_t* loop,
|
|
|
401 uv__cf_loop_signal_type_t type) {
|
|
|
402 struct uv__queue* q;
|
|
|
403 uv_fs_event_t* curr;
|
|
|
404 CFArrayRef cf_paths;
|
|
|
405 CFStringRef* paths;
|
|
|
406 unsigned int i;
|
|
|
407 int err;
|
|
|
408 unsigned int path_count;
|
|
|
409
|
|
|
410 paths = NULL;
|
|
|
411 cf_paths = NULL;
|
|
|
412 err = 0;
|
|
|
413 /* NOTE: `i` is used in deallocation loop below */
|
|
|
414 i = 0;
|
|
|
415
|
|
|
416 /* Optimization to prevent O(n^2) time spent when starting to watch
|
|
|
417 * many files simultaneously
|
|
|
418 */
|
|
|
419 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
420 if (state->fsevent_need_reschedule == 0) {
|
|
|
421 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
422 goto final;
|
|
|
423 }
|
|
|
424 state->fsevent_need_reschedule = 0;
|
|
|
425 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
426
|
|
|
427 /* Destroy previous FSEventStream */
|
|
|
428 uv__fsevents_destroy_stream(state);
|
|
|
429
|
|
|
430 /* Any failure below will be a memory failure */
|
|
|
431 err = UV_ENOMEM;
|
|
|
432
|
|
|
433 /* Create list of all watched paths */
|
|
|
434 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
435 path_count = state->fsevent_handle_count;
|
|
|
436 if (path_count != 0) {
|
|
|
437 paths = uv__malloc(sizeof(*paths) * path_count);
|
|
|
438 if (paths == NULL) {
|
|
|
439 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
440 goto final;
|
|
|
441 }
|
|
|
442
|
|
|
443 q = &state->fsevent_handles;
|
|
|
444 for (; i < path_count; i++) {
|
|
|
445 q = uv__queue_next(q);
|
|
|
446 assert(q != &state->fsevent_handles);
|
|
|
447 curr = uv__queue_data(q, uv_fs_event_t, cf_member);
|
|
|
448
|
|
|
449 assert(curr->realpath != NULL);
|
|
|
450 paths[i] =
|
|
|
451 pCFStringCreateWithFileSystemRepresentation(NULL, curr->realpath);
|
|
|
452 if (paths[i] == NULL) {
|
|
|
453 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
454 goto final;
|
|
|
455 }
|
|
|
456 }
|
|
|
457 }
|
|
|
458 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
459 err = 0;
|
|
|
460
|
|
|
461 if (path_count != 0) {
|
|
|
462 /* Create new FSEventStream */
|
|
|
463 cf_paths = pCFArrayCreate(NULL, (const void**) paths, path_count, NULL);
|
|
|
464 if (cf_paths == NULL) {
|
|
|
465 err = UV_ENOMEM;
|
|
|
466 goto final;
|
|
|
467 }
|
|
|
468 err = uv__fsevents_create_stream(state, loop, cf_paths);
|
|
|
469 }
|
|
|
470
|
|
|
471 final:
|
|
|
472 /* Deallocate all paths in case of failure */
|
|
|
473 if (err != 0) {
|
|
|
474 if (cf_paths == NULL) {
|
|
|
475 while (i != 0)
|
|
|
476 pCFRelease(paths[--i]);
|
|
|
477 uv__free(paths);
|
|
|
478 } else {
|
|
|
479 /* CFArray takes ownership of both strings and original C-array */
|
|
|
480 pCFRelease(cf_paths);
|
|
|
481 }
|
|
|
482
|
|
|
483 /* Broadcast error to all handles */
|
|
|
484 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
485 uv__queue_foreach(q, &state->fsevent_handles) {
|
|
|
486 curr = uv__queue_data(q, uv_fs_event_t, cf_member);
|
|
|
487 uv__fsevents_push_event(curr, NULL, err);
|
|
|
488 }
|
|
|
489 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
490 }
|
|
|
491
|
|
|
492 /*
|
|
|
493 * Main thread will block until the removal of handle from the list,
|
|
|
494 * we must tell it when we're ready.
|
|
|
495 *
|
|
|
496 * NOTE: This is coupled with `uv_sem_wait()` in `uv__fsevents_close`
|
|
|
497 */
|
|
|
498 if (type == kUVCFLoopSignalClosing)
|
|
|
499 uv_sem_post(&state->fsevent_sem);
|
|
|
500 }
|
|
|
501
|
|
|
502
|
|
|
503 static int uv__fsevents_global_init(void) {
|
|
|
504 static pthread_mutex_t global_init_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
505 static void* core_foundation_handle;
|
|
|
506 static void* core_services_handle;
|
|
|
507 int err;
|
|
|
508
|
|
|
509 err = 0;
|
|
|
510 pthread_mutex_lock(&global_init_mutex);
|
|
|
511 if (core_foundation_handle != NULL)
|
|
|
512 goto out;
|
|
|
513
|
|
|
514 /* The libraries are never unloaded because we currently don't have a good
|
|
|
515 * mechanism for keeping a reference count. It's unlikely to be an issue
|
|
|
516 * but if it ever becomes one, we can turn the dynamic library handles into
|
|
|
517 * per-event loop properties and have the dynamic linker keep track for us.
|
|
|
518 */
|
|
|
519 err = UV_ENOSYS;
|
|
|
520 core_foundation_handle = dlopen("/System/Library/Frameworks/"
|
|
|
521 "CoreFoundation.framework/"
|
|
|
522 "Versions/A/CoreFoundation",
|
|
|
523 RTLD_LAZY | RTLD_LOCAL);
|
|
|
524 if (core_foundation_handle == NULL)
|
|
|
525 goto out;
|
|
|
526
|
|
|
527 core_services_handle = dlopen("/System/Library/Frameworks/"
|
|
|
528 "CoreServices.framework/"
|
|
|
529 "Versions/A/CoreServices",
|
|
|
530 RTLD_LAZY | RTLD_LOCAL);
|
|
|
531 if (core_services_handle == NULL)
|
|
|
532 goto out;
|
|
|
533
|
|
|
534 err = UV_ENOENT;
|
|
|
535 #define V(handle, symbol) \
|
|
|
536 do { \
|
|
|
537 *(void **)(&p ## symbol) = dlsym((handle), #symbol); \
|
|
|
538 if (p ## symbol == NULL) \
|
|
|
539 goto out; \
|
|
|
540 } \
|
|
|
541 while (0)
|
|
|
542 V(core_foundation_handle, CFArrayCreate);
|
|
|
543 V(core_foundation_handle, CFRelease);
|
|
|
544 V(core_foundation_handle, CFRunLoopAddSource);
|
|
|
545 V(core_foundation_handle, CFRunLoopGetCurrent);
|
|
|
546 V(core_foundation_handle, CFRunLoopRemoveSource);
|
|
|
547 V(core_foundation_handle, CFRunLoopRun);
|
|
|
548 V(core_foundation_handle, CFRunLoopSourceCreate);
|
|
|
549 V(core_foundation_handle, CFRunLoopSourceSignal);
|
|
|
550 V(core_foundation_handle, CFRunLoopStop);
|
|
|
551 V(core_foundation_handle, CFRunLoopWakeUp);
|
|
|
552 V(core_foundation_handle, CFStringCreateWithFileSystemRepresentation);
|
|
|
553 V(core_foundation_handle, kCFRunLoopDefaultMode);
|
|
|
554 V(core_services_handle, FSEventStreamCreate);
|
|
|
555 V(core_services_handle, FSEventStreamInvalidate);
|
|
|
556 V(core_services_handle, FSEventStreamRelease);
|
|
|
557 V(core_services_handle, FSEventStreamScheduleWithRunLoop);
|
|
|
558 V(core_services_handle, FSEventStreamStart);
|
|
|
559 V(core_services_handle, FSEventStreamStop);
|
|
|
560 #undef V
|
|
|
561 err = 0;
|
|
|
562
|
|
|
563 out:
|
|
|
564 if (err && core_services_handle != NULL) {
|
|
|
565 dlclose(core_services_handle);
|
|
|
566 core_services_handle = NULL;
|
|
|
567 }
|
|
|
568
|
|
|
569 if (err && core_foundation_handle != NULL) {
|
|
|
570 dlclose(core_foundation_handle);
|
|
|
571 core_foundation_handle = NULL;
|
|
|
572 }
|
|
|
573
|
|
|
574 pthread_mutex_unlock(&global_init_mutex);
|
|
|
575 return err;
|
|
|
576 }
|
|
|
577
|
|
|
578
|
|
|
579 /* Runs in UV loop */
|
|
|
580 static int uv__fsevents_loop_init(uv_loop_t* loop) {
|
|
|
581 CFRunLoopSourceContext ctx;
|
|
|
582 uv__cf_loop_state_t* state;
|
|
|
583 pthread_attr_t attr;
|
|
|
584 int err;
|
|
|
585
|
|
|
586 if (loop->cf_state != NULL)
|
|
|
587 return 0;
|
|
|
588
|
|
|
589 err = uv__fsevents_global_init();
|
|
|
590 if (err)
|
|
|
591 return err;
|
|
|
592
|
|
|
593 state = uv__calloc(1, sizeof(*state));
|
|
|
594 if (state == NULL)
|
|
|
595 return UV_ENOMEM;
|
|
|
596
|
|
|
597 err = uv_mutex_init(&loop->cf_mutex);
|
|
|
598 if (err)
|
|
|
599 goto fail_mutex_init;
|
|
|
600
|
|
|
601 err = uv_sem_init(&loop->cf_sem, 0);
|
|
|
602 if (err)
|
|
|
603 goto fail_sem_init;
|
|
|
604
|
|
|
605 uv__queue_init(&loop->cf_signals);
|
|
|
606
|
|
|
607 err = uv_sem_init(&state->fsevent_sem, 0);
|
|
|
608 if (err)
|
|
|
609 goto fail_fsevent_sem_init;
|
|
|
610
|
|
|
611 err = uv_mutex_init(&state->fsevent_mutex);
|
|
|
612 if (err)
|
|
|
613 goto fail_fsevent_mutex_init;
|
|
|
614
|
|
|
615 uv__queue_init(&state->fsevent_handles);
|
|
|
616 state->fsevent_need_reschedule = 0;
|
|
|
617 state->fsevent_handle_count = 0;
|
|
|
618
|
|
|
619 memset(&ctx, 0, sizeof(ctx));
|
|
|
620 ctx.info = loop;
|
|
|
621 ctx.perform = uv__cf_loop_cb;
|
|
|
622 state->signal_source = pCFRunLoopSourceCreate(NULL, 0, &ctx);
|
|
|
623 if (state->signal_source == NULL) {
|
|
|
624 err = UV_ENOMEM;
|
|
|
625 goto fail_signal_source_create;
|
|
|
626 }
|
|
|
627
|
|
|
628 if (pthread_attr_init(&attr))
|
|
|
629 abort();
|
|
|
630
|
|
|
631 if (pthread_attr_setstacksize(&attr, uv__thread_stack_size()))
|
|
|
632 abort();
|
|
|
633
|
|
|
634 loop->cf_state = state;
|
|
|
635
|
|
|
636 /* uv_thread_t is an alias for pthread_t. */
|
|
|
637 err = UV__ERR(pthread_create(&loop->cf_thread, &attr, uv__cf_loop_runner, loop));
|
|
|
638
|
|
|
639 if (pthread_attr_destroy(&attr))
|
|
|
640 abort();
|
|
|
641
|
|
|
642 if (err)
|
|
|
643 goto fail_thread_create;
|
|
|
644
|
|
|
645 /* Synchronize threads */
|
|
|
646 uv_sem_wait(&loop->cf_sem);
|
|
|
647 return 0;
|
|
|
648
|
|
|
649 fail_thread_create:
|
|
|
650 loop->cf_state = NULL;
|
|
|
651
|
|
|
652 fail_signal_source_create:
|
|
|
653 uv_mutex_destroy(&state->fsevent_mutex);
|
|
|
654
|
|
|
655 fail_fsevent_mutex_init:
|
|
|
656 uv_sem_destroy(&state->fsevent_sem);
|
|
|
657
|
|
|
658 fail_fsevent_sem_init:
|
|
|
659 uv_sem_destroy(&loop->cf_sem);
|
|
|
660
|
|
|
661 fail_sem_init:
|
|
|
662 uv_mutex_destroy(&loop->cf_mutex);
|
|
|
663
|
|
|
664 fail_mutex_init:
|
|
|
665 uv__free(state);
|
|
|
666 return err;
|
|
|
667 }
|
|
|
668
|
|
|
669
|
|
|
670 /* Runs in UV loop */
|
|
|
671 void uv__fsevents_loop_delete(uv_loop_t* loop) {
|
|
|
672 uv__cf_loop_signal_t* s;
|
|
|
673 uv__cf_loop_state_t* state;
|
|
|
674 struct uv__queue* q;
|
|
|
675
|
|
|
676 if (loop->cf_state == NULL)
|
|
|
677 return;
|
|
|
678
|
|
|
679 if (uv__cf_loop_signal(loop, NULL, kUVCFLoopSignalRegular) != 0)
|
|
|
680 abort();
|
|
|
681
|
|
|
682 uv_thread_join(&loop->cf_thread);
|
|
|
683 uv_sem_destroy(&loop->cf_sem);
|
|
|
684 uv_mutex_destroy(&loop->cf_mutex);
|
|
|
685
|
|
|
686 /* Free any remaining data */
|
|
|
687 while (!uv__queue_empty(&loop->cf_signals)) {
|
|
|
688 q = uv__queue_head(&loop->cf_signals);
|
|
|
689 s = uv__queue_data(q, uv__cf_loop_signal_t, member);
|
|
|
690 uv__queue_remove(q);
|
|
|
691 uv__free(s);
|
|
|
692 }
|
|
|
693
|
|
|
694 /* Destroy state */
|
|
|
695 state = loop->cf_state;
|
|
|
696 uv_sem_destroy(&state->fsevent_sem);
|
|
|
697 uv_mutex_destroy(&state->fsevent_mutex);
|
|
|
698 pCFRelease(state->signal_source);
|
|
|
699 uv__free(state);
|
|
|
700 loop->cf_state = NULL;
|
|
|
701 }
|
|
|
702
|
|
|
703
|
|
|
704 /* Runs in CF thread. This is the CF loop's body */
|
|
|
705 static void* uv__cf_loop_runner(void* arg) {
|
|
|
706 uv_loop_t* loop;
|
|
|
707 uv__cf_loop_state_t* state;
|
|
|
708
|
|
|
709 loop = arg;
|
|
|
710 state = loop->cf_state;
|
|
|
711 state->loop = pCFRunLoopGetCurrent();
|
|
|
712
|
|
|
713 pCFRunLoopAddSource(state->loop,
|
|
|
714 state->signal_source,
|
|
|
715 *pkCFRunLoopDefaultMode);
|
|
|
716
|
|
|
717 uv_sem_post(&loop->cf_sem);
|
|
|
718
|
|
|
719 pCFRunLoopRun();
|
|
|
720 pCFRunLoopRemoveSource(state->loop,
|
|
|
721 state->signal_source,
|
|
|
722 *pkCFRunLoopDefaultMode);
|
|
|
723
|
|
|
724 state->loop = NULL;
|
|
|
725
|
|
|
726 return NULL;
|
|
|
727 }
|
|
|
728
|
|
|
729
|
|
|
730 /* Runs in CF thread, executed after `uv__cf_loop_signal()` */
|
|
|
731 static void uv__cf_loop_cb(void* arg) {
|
|
|
732 uv_loop_t* loop;
|
|
|
733 uv__cf_loop_state_t* state;
|
|
|
734 struct uv__queue* item;
|
|
|
735 struct uv__queue split_head;
|
|
|
736 uv__cf_loop_signal_t* s;
|
|
|
737
|
|
|
738 loop = arg;
|
|
|
739 state = loop->cf_state;
|
|
|
740
|
|
|
741 uv_mutex_lock(&loop->cf_mutex);
|
|
|
742 uv__queue_move(&loop->cf_signals, &split_head);
|
|
|
743 uv_mutex_unlock(&loop->cf_mutex);
|
|
|
744
|
|
|
745 while (!uv__queue_empty(&split_head)) {
|
|
|
746 item = uv__queue_head(&split_head);
|
|
|
747 uv__queue_remove(item);
|
|
|
748
|
|
|
749 s = uv__queue_data(item, uv__cf_loop_signal_t, member);
|
|
|
750
|
|
|
751 /* This was a termination signal */
|
|
|
752 if (s->handle == NULL)
|
|
|
753 pCFRunLoopStop(state->loop);
|
|
|
754 else
|
|
|
755 uv__fsevents_reschedule(state, loop, s->type);
|
|
|
756
|
|
|
757 uv__free(s);
|
|
|
758 }
|
|
|
759 }
|
|
|
760
|
|
|
761
|
|
|
762 /* Runs in UV loop to notify CF thread */
|
|
|
763 int uv__cf_loop_signal(uv_loop_t* loop,
|
|
|
764 uv_fs_event_t* handle,
|
|
|
765 uv__cf_loop_signal_type_t type) {
|
|
|
766 uv__cf_loop_signal_t* item;
|
|
|
767 uv__cf_loop_state_t* state;
|
|
|
768
|
|
|
769 item = uv__malloc(sizeof(*item));
|
|
|
770 if (item == NULL)
|
|
|
771 return UV_ENOMEM;
|
|
|
772
|
|
|
773 item->handle = handle;
|
|
|
774 item->type = type;
|
|
|
775
|
|
|
776 uv_mutex_lock(&loop->cf_mutex);
|
|
|
777 uv__queue_insert_tail(&loop->cf_signals, &item->member);
|
|
|
778
|
|
|
779 state = loop->cf_state;
|
|
|
780 assert(state != NULL);
|
|
|
781 pCFRunLoopSourceSignal(state->signal_source);
|
|
|
782 pCFRunLoopWakeUp(state->loop);
|
|
|
783
|
|
|
784 uv_mutex_unlock(&loop->cf_mutex);
|
|
|
785
|
|
|
786 return 0;
|
|
|
787 }
|
|
|
788
|
|
|
789
|
|
|
790 /* Runs in UV loop to initialize handle */
|
|
|
791 int uv__fsevents_init(uv_fs_event_t* handle) {
|
|
|
792 char* buf;
|
|
|
793 int err;
|
|
|
794 uv__cf_loop_state_t* state;
|
|
|
795
|
|
|
796 err = uv__fsevents_loop_init(handle->loop);
|
|
|
797 if (err)
|
|
|
798 return err;
|
|
|
799
|
|
|
800 /* Get absolute path to file */
|
|
|
801 buf = realpath(handle->path, NULL);
|
|
|
802 if (buf == NULL)
|
|
|
803 return UV__ERR(errno);
|
|
|
804 handle->realpath = uv__strdup(buf);
|
|
|
805 free(buf); /* _Not_ uv__free. */
|
|
|
806 if (handle->realpath == NULL)
|
|
|
807 return UV_ENOMEM;
|
|
|
808 handle->realpath_len = strlen(handle->realpath);
|
|
|
809
|
|
|
810 /* Initialize event queue */
|
|
|
811 uv__queue_init(&handle->cf_events);
|
|
|
812 handle->cf_error = 0;
|
|
|
813
|
|
|
814 /*
|
|
|
815 * Events will occur in other thread.
|
|
|
816 * Initialize callback for getting them back into event loop's thread
|
|
|
817 */
|
|
|
818 handle->cf_cb = uv__malloc(sizeof(*handle->cf_cb));
|
|
|
819 if (handle->cf_cb == NULL) {
|
|
|
820 err = UV_ENOMEM;
|
|
|
821 goto fail_cf_cb_malloc;
|
|
|
822 }
|
|
|
823
|
|
|
824 handle->cf_cb->data = handle;
|
|
|
825 uv_async_init(handle->loop, handle->cf_cb, uv__fsevents_cb);
|
|
|
826 handle->cf_cb->flags |= UV_HANDLE_INTERNAL;
|
|
|
827 uv_unref((uv_handle_t*) handle->cf_cb);
|
|
|
828
|
|
|
829 err = uv_mutex_init(&handle->cf_mutex);
|
|
|
830 if (err)
|
|
|
831 goto fail_cf_mutex_init;
|
|
|
832
|
|
|
833 /* Insert handle into the list */
|
|
|
834 state = handle->loop->cf_state;
|
|
|
835 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
836 uv__queue_insert_tail(&state->fsevent_handles, &handle->cf_member);
|
|
|
837 state->fsevent_handle_count++;
|
|
|
838 state->fsevent_need_reschedule = 1;
|
|
|
839 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
840
|
|
|
841 /* Reschedule FSEventStream */
|
|
|
842 assert(handle != NULL);
|
|
|
843 err = uv__cf_loop_signal(handle->loop, handle, kUVCFLoopSignalRegular);
|
|
|
844 if (err)
|
|
|
845 goto fail_loop_signal;
|
|
|
846
|
|
|
847 return 0;
|
|
|
848
|
|
|
849 fail_loop_signal:
|
|
|
850 uv_mutex_destroy(&handle->cf_mutex);
|
|
|
851
|
|
|
852 fail_cf_mutex_init:
|
|
|
853 uv__free(handle->cf_cb);
|
|
|
854 handle->cf_cb = NULL;
|
|
|
855
|
|
|
856 fail_cf_cb_malloc:
|
|
|
857 uv__free(handle->realpath);
|
|
|
858 handle->realpath = NULL;
|
|
|
859 handle->realpath_len = 0;
|
|
|
860
|
|
|
861 return err;
|
|
|
862 }
|
|
|
863
|
|
|
864
|
|
|
865 /* Runs in UV loop to de-initialize handle */
|
|
|
866 int uv__fsevents_close(uv_fs_event_t* handle) {
|
|
|
867 int err;
|
|
|
868 uv__cf_loop_state_t* state;
|
|
|
869
|
|
|
870 if (handle->cf_cb == NULL)
|
|
|
871 return UV_EINVAL;
|
|
|
872
|
|
|
873 /* Remove handle from the list */
|
|
|
874 state = handle->loop->cf_state;
|
|
|
875 uv_mutex_lock(&state->fsevent_mutex);
|
|
|
876 uv__queue_remove(&handle->cf_member);
|
|
|
877 state->fsevent_handle_count--;
|
|
|
878 state->fsevent_need_reschedule = 1;
|
|
|
879 uv_mutex_unlock(&state->fsevent_mutex);
|
|
|
880
|
|
|
881 /* Reschedule FSEventStream */
|
|
|
882 assert(handle != NULL);
|
|
|
883 err = uv__cf_loop_signal(handle->loop, handle, kUVCFLoopSignalClosing);
|
|
|
884 if (err)
|
|
|
885 return UV__ERR(err);
|
|
|
886
|
|
|
887 /* Wait for deinitialization */
|
|
|
888 uv_sem_wait(&state->fsevent_sem);
|
|
|
889
|
|
|
890 uv_close((uv_handle_t*) handle->cf_cb, (uv_close_cb) uv__free);
|
|
|
891 handle->cf_cb = NULL;
|
|
|
892
|
|
|
893 /* Free data in queue */
|
|
|
894 UV__FSEVENTS_PROCESS(handle, {
|
|
|
895 /* NOP */
|
|
|
896 });
|
|
|
897
|
|
|
898 uv_mutex_destroy(&handle->cf_mutex);
|
|
|
899 uv__free(handle->realpath);
|
|
|
900 handle->realpath = NULL;
|
|
|
901 handle->realpath_len = 0;
|
|
|
902
|
|
|
903 return 0;
|
|
|
904 }
|
|
|
905
|
|
|
906 #endif /* TARGET_OS_IPHONE */
|