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