/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "uv.h" #include "internal.h" #if TARGET_OS_IPHONE /* iOS (currently) doesn't provide the FSEvents-API (nor CoreServices) */ int uv__fsevents_init(uv_fs_event_t* handle) { return 0; } int uv__fsevents_close(uv_fs_event_t* handle) { return 0; } void uv__fsevents_loop_delete(uv_loop_t* loop) { } #else /* TARGET_OS_IPHONE */ #include #include #include #include #include #include /* These are macros to avoid "initializer element is not constant" errors * with old versions of gcc. */ #define kFSEventsModified (kFSEventStreamEventFlagItemFinderInfoMod | \ kFSEventStreamEventFlagItemModified | \ kFSEventStreamEventFlagItemInodeMetaMod | \ kFSEventStreamEventFlagItemChangeOwner | \ kFSEventStreamEventFlagItemXattrMod) #define kFSEventsRenamed (kFSEventStreamEventFlagItemCreated | \ kFSEventStreamEventFlagItemRemoved | \ kFSEventStreamEventFlagItemRenamed) #define kFSEventsSystem (kFSEventStreamEventFlagUserDropped | \ kFSEventStreamEventFlagKernelDropped | \ kFSEventStreamEventFlagEventIdsWrapped | \ kFSEventStreamEventFlagHistoryDone | \ kFSEventStreamEventFlagMount | \ kFSEventStreamEventFlagUnmount | \ kFSEventStreamEventFlagRootChanged) typedef struct uv__fsevents_event_s uv__fsevents_event_t; typedef struct uv__cf_loop_signal_s uv__cf_loop_signal_t; typedef struct uv__cf_loop_state_s uv__cf_loop_state_t; struct uv__cf_loop_signal_s { QUEUE member; uv_fs_event_t* handle; }; struct uv__fsevents_event_s { QUEUE member; int events; char path[1]; }; struct uv__cf_loop_state_s { CFRunLoopRef loop; CFRunLoopSourceRef signal_source; int fsevent_need_reschedule; FSEventStreamRef fsevent_stream; uv_sem_t fsevent_sem; uv_mutex_t fsevent_mutex; void* fsevent_handles[2]; unsigned int fsevent_handle_count; }; /* Forward declarations */ static void uv__cf_loop_cb(void* arg); static void* uv__cf_loop_runner(void* arg); static int uv__cf_loop_signal(uv_loop_t* loop, uv_fs_event_t* handle); /* Lazy-loaded by uv__fsevents_global_init(). */ static CFArrayRef (*pCFArrayCreate)(CFAllocatorRef, const void**, CFIndex, const CFArrayCallBacks*); static void (*pCFRelease)(CFTypeRef); static void (*pCFRunLoopAddSource)(CFRunLoopRef, CFRunLoopSourceRef, CFStringRef); static CFRunLoopRef (*pCFRunLoopGetCurrent)(void); static void (*pCFRunLoopRemoveSource)(CFRunLoopRef, CFRunLoopSourceRef, CFStringRef); static void (*pCFRunLoopRun)(void); static CFRunLoopSourceRef (*pCFRunLoopSourceCreate)(CFAllocatorRef, CFIndex, CFRunLoopSourceContext*); static void (*pCFRunLoopSourceSignal)(CFRunLoopSourceRef); static void (*pCFRunLoopStop)(CFRunLoopRef); static void (*pCFRunLoopWakeUp)(CFRunLoopRef); static CFStringRef (*pCFStringCreateWithFileSystemRepresentation)( CFAllocatorRef, const char*); static CFStringEncoding (*pCFStringGetSystemEncoding)(void); static CFStringRef (*pkCFRunLoopDefaultMode); static FSEventStreamRef (*pFSEventStreamCreate)(CFAllocatorRef, FSEventStreamCallback, FSEventStreamContext*, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags); static void (*pFSEventStreamFlushSync)(FSEventStreamRef); static void (*pFSEventStreamInvalidate)(FSEventStreamRef); static void (*pFSEventStreamRelease)(FSEventStreamRef); static void (*pFSEventStreamScheduleWithRunLoop)(FSEventStreamRef, CFRunLoopRef, CFStringRef); static Boolean (*pFSEventStreamStart)(FSEventStreamRef); static void (*pFSEventStreamStop)(FSEventStreamRef); #define UV__FSEVENTS_PROCESS(handle, block) \ do { \ QUEUE events; \ QUEUE* q; \ uv__fsevents_event_t* event; \ int err; \ uv_mutex_lock(&(handle)->cf_mutex); \ /* Split-off all events and empty original queue */ \ QUEUE_INIT(&events); \ if (!QUEUE_EMPTY(&(handle)->cf_events)) { \ q = QUEUE_HEAD(&(handle)->cf_events); \ QUEUE_SPLIT(&(handle)->cf_events, q, &events); \ } \ /* Get error (if any) and zero original one */ \ err = (handle)->cf_error; \ (handle)->cf_error = 0; \ uv_mutex_unlock(&(handle)->cf_mutex); \ /* Loop through events, deallocating each after processing */ \ while (!QUEUE_EMPTY(&events)) { \ q = QUEUE_HEAD(&events); \ event = QUEUE_DATA(q, uv__fsevents_event_t, member); \ QUEUE_REMOVE(q); \ /* NOTE: Checking uv__is_active() is required here, because handle \ * callback may close handle and invoking it after it will lead to \ * incorrect behaviour */ \ if (!uv__is_closing((handle)) && uv__is_active((handle))) \ block \ /* Free allocated data */ \ free(event); \ } \ if (err != 0 && !uv__is_closing((handle)) && uv__is_active((handle))) \ (handle)->cb((handle), NULL, 0, err); \ } while (0) /* Runs in UV loop's thread, when there're events to report to handle */ static void uv__fsevents_cb(uv_async_t* cb, int status) { uv_fs_event_t* handle; handle = cb->data; UV__FSEVENTS_PROCESS(handle, { handle->cb(handle, event->path[0] ? event->path : NULL, event->events, 0); }); } /* Runs in CF thread, pushed event into handle's event list */ static void uv__fsevents_push_event(uv_fs_event_t* handle, QUEUE* events, int err) { assert(events != NULL || err != 0); uv_mutex_lock(&handle->cf_mutex); /* Concatenate two queues */ if (events != NULL) QUEUE_ADD(&handle->cf_events, events); /* Propagate error */ if (err != 0) handle->cf_error = err; uv_mutex_unlock(&handle->cf_mutex); uv_async_send(handle->cf_cb); } /* Runs in CF thread, when there're events in FSEventStream */ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, void* info, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { size_t i; int len; char** paths; char* path; char* pos; uv_fs_event_t* handle; QUEUE* q; uv_loop_t* loop; uv__cf_loop_state_t* state; uv__fsevents_event_t* event; QUEUE head; loop = info; state = loop->cf_state; assert(state != NULL); paths = eventPaths; /* For each handle */ uv_mutex_lock(&state->fsevent_mutex); QUEUE_FOREACH(q, &state->fsevent_handles) { handle = QUEUE_DATA(q, uv_fs_event_t, cf_member); QUEUE_INIT(&head); /* Process and filter out events */ for (i = 0; i < numEvents; i++) { /* Ignore system events */ if (eventFlags[i] & kFSEventsSystem) continue; path = paths[i]; len = strlen(path); /* Filter out paths that are outside handle's request */ if (strncmp(path, handle->realpath, handle->realpath_len) != 0) continue; if (handle->realpath_len > 1 || *handle->realpath != '/') { path += handle->realpath_len; len -= handle->realpath_len; /* Skip forward slash */ if (*path != '\0') { path++; len--; } } #ifdef MAC_OS_X_VERSION_10_7 /* Ignore events with path equal to directory itself */ if (len == 0) continue; #endif /* MAC_OS_X_VERSION_10_7 */ /* Do not emit events from subdirectories (without option set) */ if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != 0) { pos = strchr(path + 1, '/'); if (pos != NULL) continue; } #ifndef MAC_OS_X_VERSION_10_7 path = ""; len = 0; #endif /* MAC_OS_X_VERSION_10_7 */ event = malloc(sizeof(*event) + len); if (event == NULL) break; memset(event, 0, sizeof(*event)); memcpy(event->path, path, len + 1); if ((eventFlags[i] & kFSEventsModified) != 0 && (eventFlags[i] & kFSEventsRenamed) == 0) event->events = UV_CHANGE; else event->events = UV_RENAME; QUEUE_INSERT_TAIL(&head, &event->member); } if (!QUEUE_EMPTY(&head)) uv__fsevents_push_event(handle, &head, 0); } uv_mutex_unlock(&state->fsevent_mutex); } /* Runs in CF thread */ static int uv__fsevents_create_stream(uv_loop_t* loop, CFArrayRef paths) { uv__cf_loop_state_t* state; FSEventStreamContext ctx; FSEventStreamRef ref; CFAbsoluteTime latency; FSEventStreamCreateFlags flags; /* Initialize context */ ctx.version = 0; ctx.info = loop; ctx.retain = NULL; ctx.release = NULL; ctx.copyDescription = NULL; latency = 0.05; /* Explanation of selected flags: * 1. NoDefer - without this flag, events that are happening continuously * (i.e. each event is happening after time interval less than `latency`, * counted from previous event), will be deferred and passed to callback * once they'll either fill whole OS buffer, or when this continuous stream * will stop (i.e. there'll be delay between events, bigger than * `latency`). * Specifying this flag will invoke callback after `latency` time passed * since event. * 2. FileEvents - fire callback for file changes too (by default it is firing * it only for directory changes). */ flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents; /* * NOTE: It might sound like a good idea to remember last seen StreamEventId, * but in reality one dir might have last StreamEventId less than, the other, * that is being watched now. Which will cause FSEventStream API to report * changes to files from the past. */ ref = pFSEventStreamCreate(NULL, &uv__fsevents_event_cb, &ctx, paths, kFSEventStreamEventIdSinceNow, latency, flags); assert(ref != NULL); state = loop->cf_state; pFSEventStreamScheduleWithRunLoop(ref, state->loop, *pkCFRunLoopDefaultMode); if (!pFSEventStreamStart(ref)) { pFSEventStreamInvalidate(ref); pFSEventStreamRelease(ref); return -EMFILE; } state->fsevent_stream = ref; return 0; } /* Runs in CF thread */ static void uv__fsevents_destroy_stream(uv_loop_t* loop) { uv__cf_loop_state_t* state; state = loop->cf_state; if (state->fsevent_stream == NULL) return; /* Flush all accumulated events */ pFSEventStreamFlushSync(state->fsevent_stream); /* Stop emitting events */ pFSEventStreamStop(state->fsevent_stream); /* Release stream */ pFSEventStreamInvalidate(state->fsevent_stream); pFSEventStreamRelease(state->fsevent_stream); state->fsevent_stream = NULL; } /* Runs in CF thread, when there're new fsevent handles to add to stream */ static void uv__fsevents_reschedule(uv_fs_event_t* handle) { uv__cf_loop_state_t* state; QUEUE* q; uv_fs_event_t* curr; CFArrayRef cf_paths; CFStringRef* paths; unsigned int i; int err; unsigned int path_count; state = handle->loop->cf_state; paths = NULL; cf_paths = NULL; err = 0; /* NOTE: `i` is used in deallocation loop below */ i = 0; /* Optimization to prevent O(n^2) time spent when starting to watch * many files simultaneously */ uv_mutex_lock(&state->fsevent_mutex); if (state->fsevent_need_reschedule == 0) { uv_mutex_unlock(&state->fsevent_mutex); goto final; } state->fsevent_need_reschedule = 0; uv_mutex_unlock(&state->fsevent_mutex); /* Destroy previous FSEventStream */ uv__fsevents_destroy_stream(handle->loop); /* Any failure below will be a memory failure */ err = -ENOMEM; /* Create list of all watched paths */ uv_mutex_lock(&state->fsevent_mutex); path_count = state->fsevent_handle_count; if (path_count != 0) { paths = malloc(sizeof(*paths) * path_count); if (paths == NULL) { uv_mutex_unlock(&state->fsevent_mutex); goto final; } q = &state->fsevent_handles; for (; i < path_count; i++) { q = QUEUE_NEXT(q); assert(q != &state->fsevent_handles); curr = QUEUE_DATA(q, uv_fs_event_t, cf_member); assert(curr->realpath != NULL); paths[i] = pCFStringCreateWithFileSystemRepresentation(NULL, curr->realpath); if (paths[i] == NULL) { uv_mutex_unlock(&state->fsevent_mutex); goto final; } } } uv_mutex_unlock(&state->fsevent_mutex); err = 0; if (path_count != 0) { /* Create new FSEventStream */ cf_paths = pCFArrayCreate(NULL, (const void**) paths, path_count, NULL); if (cf_paths == NULL) { err = -ENOMEM; goto final; } err = uv__fsevents_create_stream(handle->loop, cf_paths); } final: /* Deallocate all paths in case of failure */ if (err != 0) { if (cf_paths == NULL) { while (i != 0) pCFRelease(paths[--i]); free(paths); } else { /* CFArray takes ownership of both strings and original C-array */ pCFRelease(cf_paths); } /* Broadcast error to all handles */ uv_mutex_lock(&state->fsevent_mutex); QUEUE_FOREACH(q, &state->fsevent_handles) { curr = QUEUE_DATA(q, uv_fs_event_t, cf_member); uv__fsevents_push_event(curr, NULL, err); } uv_mutex_unlock(&state->fsevent_mutex); } /* * Main thread will block until the removal of handle from the list, * we must tell it when we're ready. * * NOTE: This is coupled with `uv_sem_wait()` in `uv__fsevents_close` */ if (!uv__is_active(handle)) uv_sem_post(&state->fsevent_sem); } static int uv__fsevents_global_init(void) { static pthread_mutex_t global_init_mutex = PTHREAD_MUTEX_INITIALIZER; static void* core_foundation_handle; static void* core_services_handle; int err; err = 0; pthread_mutex_lock(&global_init_mutex); if (core_foundation_handle != NULL) goto out; /* The libraries are never unloaded because we currently don't have a good * mechanism for keeping a reference count. It's unlikely to be an issue * but if it ever becomes one, we can turn the dynamic library handles into * per-event loop properties and have the dynamic linker keep track for us. */ err = -ENOSYS; core_foundation_handle = dlopen("/System/Library/Frameworks/" "CoreFoundation.framework/" "Versions/A/CoreFoundation", RTLD_LAZY | RTLD_LOCAL); if (core_foundation_handle == NULL) goto out; core_services_handle = dlopen("/System/Library/Frameworks/" "CoreServices.framework/" "Versions/A/CoreServices", RTLD_LAZY | RTLD_LOCAL); if (core_services_handle == NULL) goto out; err = -ENOENT; #define V(handle, symbol) \ do { \ p ## symbol = dlsym((handle), #symbol); \ if (p ## symbol == NULL) \ goto out; \ } \ while (0) V(core_foundation_handle, CFArrayCreate); V(core_foundation_handle, CFRelease); V(core_foundation_handle, CFRunLoopAddSource); V(core_foundation_handle, CFRunLoopGetCurrent); V(core_foundation_handle, CFRunLoopRemoveSource); V(core_foundation_handle, CFRunLoopRun); V(core_foundation_handle, CFRunLoopSourceCreate); V(core_foundation_handle, CFRunLoopSourceSignal); V(core_foundation_handle, CFRunLoopStop); V(core_foundation_handle, CFRunLoopWakeUp); V(core_foundation_handle, CFStringCreateWithFileSystemRepresentation); V(core_foundation_handle, CFStringGetSystemEncoding); V(core_foundation_handle, kCFRunLoopDefaultMode); V(core_services_handle, FSEventStreamCreate); V(core_services_handle, FSEventStreamFlushSync); V(core_services_handle, FSEventStreamInvalidate); V(core_services_handle, FSEventStreamRelease); V(core_services_handle, FSEventStreamScheduleWithRunLoop); V(core_services_handle, FSEventStreamStart); V(core_services_handle, FSEventStreamStop); #undef V err = 0; out: if (err && core_services_handle != NULL) { dlclose(core_services_handle); core_services_handle = NULL; } if (err && core_foundation_handle != NULL) { dlclose(core_foundation_handle); core_foundation_handle = NULL; } pthread_mutex_unlock(&global_init_mutex); return err; } /* Runs in UV loop */ static int uv__fsevents_loop_init(uv_loop_t* loop) { CFRunLoopSourceContext ctx; uv__cf_loop_state_t* state; pthread_attr_t attr_storage; pthread_attr_t* attr; int err; if (loop->cf_state != NULL) return 0; err = uv__fsevents_global_init(); if (err) return err; state = calloc(1, sizeof(*state)); if (state == NULL) return -ENOMEM; err = uv_mutex_init(&loop->cf_mutex); if (err) goto fail_mutex_init; err = uv_sem_init(&loop->cf_sem, 0); if (err) goto fail_sem_init; QUEUE_INIT(&loop->cf_signals); err = uv_sem_init(&state->fsevent_sem, 0); if (err) goto fail_fsevent_sem_init; err = uv_mutex_init(&state->fsevent_mutex); if (err) goto fail_fsevent_mutex_init; QUEUE_INIT(&state->fsevent_handles); state->fsevent_need_reschedule = 0; state->fsevent_handle_count = 0; memset(&ctx, 0, sizeof(ctx)); ctx.info = loop; ctx.perform = uv__cf_loop_cb; state->signal_source = pCFRunLoopSourceCreate(NULL, 0, &ctx); if (state->signal_source == NULL) { err = -ENOMEM; goto fail_signal_source_create; } /* In the unlikely event that pthread_attr_init() fails, create the thread * with the default stack size. We'll use a little more address space but * that in itself is not a fatal error. */ attr = &attr_storage; if (pthread_attr_init(attr)) attr = NULL; if (attr != NULL) if (pthread_attr_setstacksize(attr, 4 * PTHREAD_STACK_MIN)) abort(); loop->cf_state = state; /* uv_thread_t is an alias for pthread_t. */ err = -pthread_create(&loop->cf_thread, attr, uv__cf_loop_runner, loop); if (attr != NULL) pthread_attr_destroy(attr); if (err) goto fail_thread_create; /* Synchronize threads */ uv_sem_wait(&loop->cf_sem); return 0; fail_thread_create: loop->cf_state = NULL; fail_signal_source_create: uv_mutex_destroy(&state->fsevent_mutex); fail_fsevent_mutex_init: uv_sem_destroy(&state->fsevent_sem); fail_fsevent_sem_init: uv_sem_destroy(&loop->cf_sem); fail_sem_init: uv_mutex_destroy(&loop->cf_mutex); fail_mutex_init: free(state); return err; } /* Runs in UV loop */ void uv__fsevents_loop_delete(uv_loop_t* loop) { uv__cf_loop_signal_t* s; uv__cf_loop_state_t* state; QUEUE* q; if (loop->cf_state == NULL) return; if (uv__cf_loop_signal(loop, NULL) != 0) abort(); uv_thread_join(&loop->cf_thread); uv_sem_destroy(&loop->cf_sem); uv_mutex_destroy(&loop->cf_mutex); /* Free any remaining data */ while (!QUEUE_EMPTY(&loop->cf_signals)) { q = QUEUE_HEAD(&loop->cf_signals); s = QUEUE_DATA(q, uv__cf_loop_signal_t, member); QUEUE_REMOVE(q); free(s); } /* Destroy state */ state = loop->cf_state; uv_sem_destroy(&state->fsevent_sem); uv_mutex_destroy(&state->fsevent_mutex); pCFRelease(state->signal_source); free(state); loop->cf_state = NULL; } /* Runs in CF thread. This is the CF loop's body */ static void* uv__cf_loop_runner(void* arg) { uv_loop_t* loop; uv__cf_loop_state_t* state; loop = arg; state = loop->cf_state; state->loop = pCFRunLoopGetCurrent(); pCFRunLoopAddSource(state->loop, state->signal_source, *pkCFRunLoopDefaultMode); uv_sem_post(&loop->cf_sem); pCFRunLoopRun(); pCFRunLoopRemoveSource(state->loop, state->signal_source, *pkCFRunLoopDefaultMode); return NULL; } /* Runs in CF thread, executed after `uv__cf_loop_signal()` */ static void uv__cf_loop_cb(void* arg) { uv_loop_t* loop; uv__cf_loop_state_t* state; QUEUE* item; QUEUE split_head; uv__cf_loop_signal_t* s; loop = arg; state = loop->cf_state; QUEUE_INIT(&split_head); uv_mutex_lock(&loop->cf_mutex); if (!QUEUE_EMPTY(&loop->cf_signals)) { QUEUE* split_pos = QUEUE_HEAD(&loop->cf_signals); QUEUE_SPLIT(&loop->cf_signals, split_pos, &split_head); } uv_mutex_unlock(&loop->cf_mutex); while (!QUEUE_EMPTY(&split_head)) { item = QUEUE_HEAD(&split_head); s = QUEUE_DATA(item, uv__cf_loop_signal_t, member); /* This was a termination signal */ if (s->handle == NULL) pCFRunLoopStop(state->loop); else uv__fsevents_reschedule(s->handle); QUEUE_REMOVE(item); free(s); } } /* Runs in UV loop to notify CF thread */ int uv__cf_loop_signal(uv_loop_t* loop, uv_fs_event_t* handle) { uv__cf_loop_signal_t* item; uv__cf_loop_state_t* state; item = malloc(sizeof(*item)); if (item == NULL) return -ENOMEM; item->handle = handle; uv_mutex_lock(&loop->cf_mutex); QUEUE_INSERT_TAIL(&loop->cf_signals, &item->member); uv_mutex_unlock(&loop->cf_mutex); state = loop->cf_state; assert(state != NULL); pCFRunLoopSourceSignal(state->signal_source); pCFRunLoopWakeUp(state->loop); return 0; } /* Runs in UV loop to initialize handle */ int uv__fsevents_init(uv_fs_event_t* handle) { int err; uv__cf_loop_state_t* state; err = uv__fsevents_loop_init(handle->loop); if (err) return err; /* Get absolute path to file */ handle->realpath = realpath(handle->filename, NULL); if (handle->realpath == NULL) return -errno; handle->realpath_len = strlen(handle->realpath); /* Initialize event queue */ QUEUE_INIT(&handle->cf_events); handle->cf_error = 0; /* * Events will occur in other thread. * Initialize callback for getting them back into event loop's thread */ handle->cf_cb = malloc(sizeof(*handle->cf_cb)); if (handle->cf_cb == NULL) { err = -ENOMEM; goto fail_cf_cb_malloc; } handle->cf_cb->data = handle; uv_async_init(handle->loop, handle->cf_cb, uv__fsevents_cb); handle->cf_cb->flags |= UV__HANDLE_INTERNAL; uv_unref((uv_handle_t*) handle->cf_cb); err = uv_mutex_init(&handle->cf_mutex); if (err) goto fail_cf_mutex_init; /* Insert handle into the list */ state = handle->loop->cf_state; uv_mutex_lock(&state->fsevent_mutex); QUEUE_INSERT_TAIL(&state->fsevent_handles, &handle->cf_member); state->fsevent_handle_count++; state->fsevent_need_reschedule = 1; uv_mutex_unlock(&state->fsevent_mutex); /* Reschedule FSEventStream */ assert(handle != NULL); err = uv__cf_loop_signal(handle->loop, handle); if (err) goto fail_loop_signal; return 0; fail_loop_signal: uv_mutex_destroy(&handle->cf_mutex); fail_cf_mutex_init: free(handle->cf_cb); handle->cf_cb = NULL; fail_cf_cb_malloc: free(handle->realpath); handle->realpath = NULL; handle->realpath_len = 0; return err; } /* Runs in UV loop to de-initialize handle */ int uv__fsevents_close(uv_fs_event_t* handle) { int err; uv__cf_loop_state_t* state; if (handle->cf_cb == NULL) return -EINVAL; /* Remove handle from the list */ state = handle->loop->cf_state; uv_mutex_lock(&state->fsevent_mutex); QUEUE_REMOVE(&handle->cf_member); state->fsevent_handle_count--; state->fsevent_need_reschedule = 1; uv_mutex_unlock(&state->fsevent_mutex); /* Reschedule FSEventStream */ assert(handle != NULL); err = uv__cf_loop_signal(handle->loop, handle); if (err) return -err; /* Wait for deinitialization */ uv_sem_wait(&state->fsevent_sem); uv_close((uv_handle_t*) handle->cf_cb, (uv_close_cb) free); handle->cf_cb = NULL; /* Free data in queue */ UV__FSEVENTS_PROCESS(handle, { /* NOP */ }); uv_mutex_destroy(&handle->cf_mutex); free(handle->realpath); handle->realpath = NULL; handle->realpath_len = 0; return 0; } #endif /* TARGET_OS_IPHONE */