static bool portfs_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct portfs_root_state *state = root->watch; uint_t i, n; struct timeval now; unused_parameter(watcher); errno = 0; n = 1; if (port_getn(state->port_fd, state->portevents, sizeof(state->portevents) / sizeof(state->portevents[0]), &n, NULL)) { if (errno == EINTR) { return false; } w_log(W_LOG_FATAL, "port_getn: %s\n", strerror(errno)); } w_log(W_LOG_DBG, "port_getn: n=%u\n", n); if (n == 0) { return false; } for (i = 0; i < n; i++) { if (IS_DIR_BIT_SET(state->portevents[i].portev_user)) { struct watchman_dir *dir = DECODE_DIR(state->portevents[i].portev_user); uint32_t pe = state->portevents[i].portev_events; w_log(W_LOG_DBG, "port: dir %.*s [0x%x]\n", dir->path->len, dir->path->buf, pe); if ((pe & (FILE_RENAME_FROM|UNMOUNTED|MOUNTEDOVER|FILE_DELETE)) && w_string_equal(dir->path, root->root_path)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved (code 0x%x), canceling watch\n", root->root_path->buf, pe); w_root_cancel(root); return false; } w_pending_coll_add(coll, dir->path, false, now, true); } else { struct watchman_file *file = state->portevents[i].portev_user; w_string_t *path; path = w_string_path_cat(file->parent->path, file->name); w_pending_coll_add(coll, path, true, now, true); w_log(W_LOG_DBG, "port: file %.*s\n", path->len, path->buf); w_string_delref(path); } } return true; }
static bool kqueue_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct kqueue_root_state *state = root->watch; int n; int i; struct timespec ts = { 0, 0 }; struct timeval now; unused_parameter(watcher); errno = 0; w_log(W_LOG_DBG, "kqueue(%s)\n", root->root_path->buf); n = kevent(state->kq_fd, NULL, 0, state->keventbuf, sizeof(state->keventbuf) / sizeof(state->keventbuf[0]), &ts); w_log(W_LOG_DBG, "consume_kqueue: %s n=%d err=%s\n", root->root_path->buf, n, strerror(errno)); if (root->cancelled) { return 0; } gettimeofday(&now, NULL); for (i = 0; n > 0 && i < n; i++) { uint32_t fflags = state->keventbuf[i].fflags; if (IS_DIR_BIT_SET(state->keventbuf[i].udata)) { struct watchman_dir *dir = DECODE_DIR(state->keventbuf[i].udata); w_log(W_LOG_DBG, " KQ dir %s [0x%x]\n", dir->path->buf, fflags); if ((fflags & (NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE)) && w_string_equal(dir->path, root->root_path)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved [code 0x%x], canceling watch\n", root->root_path->buf, fflags); w_root_cancel(root); return 0; } w_pending_coll_add(coll, dir->path, false, now, false); } else { // NetBSD defines udata as intptr type, so the cast is necessary struct watchman_file *file = (void *)state->keventbuf[i].udata; w_string_t *path; path = w_string_path_cat(file->parent->path, file->name); w_pending_coll_add(coll, path, true, now, true); w_log(W_LOG_DBG, " KQ file %.*s [0x%x]\n", path->len, path->buf, fflags); w_string_delref(path); } } return n > 0; }
static bool kqueue_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct kqueue_root_state *state = root->watch; int n; int i; struct timespec ts = { 0, 0 }; struct timeval now; unused_parameter(watcher); errno = 0; n = kevent(state->kq_fd, NULL, 0, state->keventbuf, sizeof(state->keventbuf) / sizeof(state->keventbuf[0]), &ts); w_log(W_LOG_DBG, "consume_kqueue: %s n=%d err=%s\n", root->root_path->buf, n, strerror(errno)); if (root->cancelled) { return 0; } gettimeofday(&now, NULL); for (i = 0; n > 0 && i < n; i++) { uint32_t fflags = state->keventbuf[i].fflags; bool is_dir = IS_DIR_BIT_SET(state->keventbuf[i].udata); w_string_t *path; char flags_label[128]; int fd = state->keventbuf[i].ident; w_expand_flags(kflags, fflags, flags_label, sizeof(flags_label)); pthread_mutex_lock(&state->lock); path = w_ht_val_ptr(w_ht_get(state->fd_to_name, fd)); if (!path) { // Was likely a buffered notification for something that we decided // to stop watching w_log(W_LOG_DBG, " KQ notif for fd=%d; flags=0x%x %s no ref for it in fd_to_name\n", fd, fflags, flags_label); pthread_mutex_unlock(&state->lock); continue; } w_string_addref(path); w_log(W_LOG_DBG, " KQ fd=%d path %s [0x%x %s]\n", fd, path->buf, fflags, flags_label); if ((fflags & (NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE))) { struct kevent k; if (w_string_equal(path, root->root_path)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved [code 0x%x], canceling watch\n", root->root_path->buf, fflags); w_root_cancel(root); pthread_mutex_unlock(&state->lock); return 0; } // Remove our watch bits memset(&k, 0, sizeof(k)); EV_SET(&k, fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); kevent(state->kq_fd, &k, 1, NULL, 0, 0); w_ht_del(state->name_to_fd, w_ht_ptr_val(path)); w_ht_del(state->fd_to_name, fd); } pthread_mutex_unlock(&state->lock); w_pending_coll_add(coll, path, !is_dir, now, !is_dir); w_string_delref(path); } return n > 0; }
static void *readchanges_thread(void *arg) { w_root_t *root = arg; struct winwatch_root_state *state = root->watch; DWORD size = WATCHMAN_BATCH_LIMIT * (sizeof(FILE_NOTIFY_INFORMATION) + 512); char *buf; DWORD err, filter; OVERLAPPED olap; BOOL initiate_read = true; HANDLE handles[2] = { state->olap, state->ping }; DWORD bytes; w_set_thread_name("readchange %.*s", root->root_path->len, root->root_path->buf); // Block until winmatch_root_st is waiting for our initialization pthread_mutex_lock(&state->mtx); filter = FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME| FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_SIZE| FILE_NOTIFY_CHANGE_LAST_WRITE; memset(&olap, 0, sizeof(olap)); olap.hEvent = state->olap; buf = malloc(size); if (!buf) { w_log(W_LOG_ERR, "failed to allocate %u bytes for dirchanges buf\n", size); goto out; } if (!ReadDirectoryChangesW(state->dir_handle, buf, size, TRUE, filter, NULL, &olap, NULL)) { err = GetLastError(); w_log(W_LOG_ERR, "ReadDirectoryChangesW: failed, cancel watch. %s\n", win32_strerror(err)); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); goto out; } // Signal that we are done with init. We MUST do this AFTER our first // successful ReadDirectoryChangesW, otherwise there is a race condition // where we'll miss observing the cookie for a query that comes in // after we've crawled but before the watch is established. w_log(W_LOG_DBG, "ReadDirectoryChangesW signalling as init done"); pthread_cond_signal(&state->cond); pthread_mutex_unlock(&state->mtx); initiate_read = false; // The state->mutex must not be held when we enter the loop while (!root->cancelled) { if (initiate_read) { if (!ReadDirectoryChangesW(state->dir_handle, buf, size, TRUE, filter, NULL, &olap, NULL)) { err = GetLastError(); w_log(W_LOG_ERR, "ReadDirectoryChangesW: failed, cancel watch. %s\n", win32_strerror(err)); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); break; } else { initiate_read = false; } } w_log(W_LOG_DBG, "waiting for change notifications"); DWORD status = WaitForMultipleObjects(2, handles, FALSE, INFINITE); if (status == WAIT_OBJECT_0) { bytes = 0; if (!GetOverlappedResult(state->dir_handle, &olap, &bytes, FALSE)) { err = GetLastError(); w_log(W_LOG_ERR, "overlapped ReadDirectoryChangesW(%s): 0x%x %s\n", root->root_path->buf, err, win32_strerror(err)); if (err == ERROR_INVALID_PARAMETER && size > NETWORK_BUF_SIZE) { // May be a network buffer related size issue; the docs say that // we can hit this when watching a UNC path. Let's downsize and // retry the read just one time w_log(W_LOG_ERR, "retrying watch for possible network location %s " "with smaller buffer\n", root->root_path->buf); size = NETWORK_BUF_SIZE; initiate_read = true; continue; } if (err == ERROR_NOTIFY_ENUM_DIR) { w_root_schedule_recrawl(root, "ERROR_NOTIFY_ENUM_DIR"); } else { w_log(W_LOG_ERR, "Cancelling watch for %s\n", root->root_path->buf); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); break; } } else { PFILE_NOTIFY_INFORMATION not = (PFILE_NOTIFY_INFORMATION)buf; struct winwatch_changed_item *head = NULL, *tail = NULL; while (true) { struct winwatch_changed_item *item; DWORD n_chars; w_string_t *name, *full; // FileNameLength is in BYTES, but FileName is WCHAR n_chars = not->FileNameLength / sizeof(not->FileName[0]); name = w_string_new_wchar(not->FileName, n_chars); full = w_string_path_cat(root->root_path, name); w_string_delref(name); if (w_is_ignored(root, full->buf, full->len)) { w_string_delref(full); } else { item = calloc(1, sizeof(*item)); item->name = full; if (tail) { tail->next = item; } else { head = item; } tail = item; } // Advance to next item if (not->NextEntryOffset == 0) { break; } not = (PFILE_NOTIFY_INFORMATION)(not->NextEntryOffset + (char*)not); } if (tail) { pthread_mutex_lock(&state->mtx); if (state->tail) { state->tail->next = head; } else { state->head = head; } state->tail = tail; pthread_mutex_unlock(&state->mtx); pthread_cond_signal(&state->cond); } ResetEvent(state->olap); initiate_read = true; } } else if (status == WAIT_OBJECT_0 + 1) { w_log(W_LOG_ERR, "signalled\n"); break; } else { w_log(W_LOG_ERR, "impossible wait status=%d\n", status); break; } } pthread_mutex_lock(&state->mtx); out: // Signal to winwatch_root_start that we're done initializing in // the failure path. We'll also do this after we've completed // the run loop in the success path; it's a spurious wakeup but // harmless and saves us from adding and setting a control flag // in each of the failure `goto` statements. winwatch_root_dtor // will `pthread_join` us before `state` is freed. pthread_cond_signal(&state->cond); pthread_mutex_unlock(&state->mtx); if (buf) { free(buf); } w_log(W_LOG_DBG, "done\n"); w_root_delref(root); return NULL; }
static void process_inotify_event( w_root_t *root, struct watchman_pending_collection *coll, struct inotify_event *ine, struct timeval now) { struct inot_root_state *state = root->watch; char flags_label[128]; w_expand_flags(inflags, ine->mask, flags_label, sizeof(flags_label)); w_log(W_LOG_DBG, "notify: wd=%d mask=0x%x %s %s\n", ine->wd, ine->mask, flags_label, ine->len > 0 ? ine->name : ""); if (ine->wd == -1 && (ine->mask & IN_Q_OVERFLOW)) { /* we missed something, will need to re-crawl */ w_root_schedule_recrawl(root, "IN_Q_OVERFLOW"); } else if (ine->wd != -1) { w_string_t *dir_name = NULL; w_string_t *name = NULL; char buf[WATCHMAN_NAME_MAX]; int pending_flags = W_PENDING_VIA_NOTIFY; pthread_mutex_lock(&state->lock); dir_name = w_ht_val_ptr(w_ht_get(state->wd_to_name, ine->wd)); if (dir_name) { w_string_addref(dir_name); } pthread_mutex_unlock(&state->lock); if (dir_name) { if (ine->len > 0) { snprintf(buf, sizeof(buf), "%.*s/%s", dir_name->len, dir_name->buf, ine->name); name = w_string_new(buf); } else { name = dir_name; w_string_addref(name); } } if (ine->len > 0 && (ine->mask & (IN_MOVED_FROM|IN_ISDIR)) == (IN_MOVED_FROM|IN_ISDIR)) { struct pending_move mv; // record this as a pending move, so that we can automatically // watch the target when we get the other side of it. mv.created = now.tv_sec; mv.name = name; pthread_mutex_lock(&state->lock); if (!w_ht_replace(state->move_map, ine->cookie, w_ht_ptr_val(&mv))) { w_log(W_LOG_FATAL, "failed to store %" PRIx32 " -> %s in move map\n", ine->cookie, name->buf); } pthread_mutex_unlock(&state->lock); w_log(W_LOG_DBG, "recording move_from %" PRIx32 " %s\n", ine->cookie, name->buf); } if (ine->len > 0 && (ine->mask & (IN_MOVED_TO|IN_ISDIR)) == (IN_MOVED_FROM|IN_ISDIR)) { struct pending_move *old; pthread_mutex_lock(&state->lock); old = w_ht_val_ptr(w_ht_get(state->move_map, ine->cookie)); if (old) { int wd = inotify_add_watch(state->infd, name->buf, WATCHMAN_INOTIFY_MASK); if (wd == -1) { if (errno == ENOSPC || errno == ENOMEM) { // Limits exceeded, no recovery from our perspective set_poison_state(root, name, now, "inotify-add-watch", errno, inot_strerror(errno)); } else { w_log(W_LOG_DBG, "add_watch: %s %s\n", name->buf, inot_strerror(errno)); } } else { w_log(W_LOG_DBG, "moved %s -> %s\n", old->name->buf, name->buf); w_ht_replace(state->wd_to_name, wd, w_ht_ptr_val(name)); } } else { w_log(W_LOG_DBG, "move: cookie=%" PRIx32 " not found in move map %s\n", ine->cookie, name->buf); } pthread_mutex_unlock(&state->lock); } if (dir_name) { if ((ine->mask & (IN_UNMOUNT|IN_IGNORED|IN_DELETE_SELF|IN_MOVE_SELF))) { w_string_t *pname; if (w_string_equal(root->root_path, name)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved, canceling watch\n", root->root_path->buf); w_string_delref(name); w_string_delref(dir_name); w_root_cancel(root); return; } // We need to examine the parent and crawl down pname = w_string_dirname(name); w_log(W_LOG_DBG, "mask=%x, focus on parent: %.*s\n", ine->mask, pname->len, pname->buf); w_string_delref(name); name = pname; pending_flags |= W_PENDING_RECURSIVE; } if (ine->mask & (IN_CREATE|IN_DELETE)) { pending_flags |= W_PENDING_RECURSIVE; } w_log(W_LOG_DBG, "add_pending for inotify mask=%x %.*s\n", ine->mask, name->len, name->buf); w_pending_coll_add(coll, name, now, pending_flags); w_string_delref(name); // The kernel removed the wd -> name mapping, so let's update // our state here also if ((ine->mask & IN_IGNORED) != 0) { w_log(W_LOG_DBG, "mask=%x: remove watch %d %.*s\n", ine->mask, ine->wd, dir_name->len, dir_name->buf); pthread_mutex_lock(&state->lock); w_ht_del(state->wd_to_name, ine->wd); pthread_mutex_unlock(&state->lock); } w_string_delref(dir_name); } else if ((ine->mask & (IN_MOVE_SELF|IN_IGNORED)) == 0) { // If we can't resolve the dir, and this isn't notification // that it has gone away, then we want to recrawl to fix // up our state. w_log(W_LOG_ERR, "wanted dir %d for mask %x but not found %.*s\n", ine->wd, ine->mask, ine->len, ine->name); w_root_schedule_recrawl(root, "dir missing from internal state"); } } }
static void fse_callback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { size_t i; char **paths = eventPaths; w_root_t *root = clientCallBackInfo; char pathbuf[WATCHMAN_NAME_MAX]; struct watchman_fsevent *head = NULL, *tail = NULL, *evt; struct fsevents_root_state *state = root->watch; unused_parameter(streamRef); unused_parameter(eventIds); unused_parameter(eventFlags); for (i = 0; i < numEvents; i++) { uint32_t len; len = strlen(paths[i]); if (len >= sizeof(pathbuf)-1) { w_log(W_LOG_DBG, "FSEvents: %s name is too big :-(\n", paths[i]); w_root_cancel(root); break; } strcpy(pathbuf, paths[i]); while (pathbuf[len-1] == '/') { pathbuf[len-1] = '\0'; len--; } if (w_is_ignored(root, paths[i], len)) { continue; } evt = calloc(1, sizeof(*evt)); if (!evt) { w_log(W_LOG_DBG, "FSEvents: OOM!"); w_root_cancel(root); break; } evt->path = w_string_new(pathbuf); evt->flags = eventFlags[i]; if (tail) { tail->next = evt; } else { head = evt; } tail = evt; w_log(W_LOG_DBG, "fse_thread: add %s %" PRIx32 "\n", pathbuf, evt->flags); } pthread_mutex_lock(&state->fse_mtx); if (state->fse_tail) { state->fse_tail->next = head; } else { state->fse_head = head; } state->fse_tail = tail; pthread_mutex_unlock(&state->fse_mtx); pthread_cond_signal(&state->fse_cond); }
static bool fsevents_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root) { struct watchman_fsevent *head, *evt; int n = 0; struct timeval now; bool recurse; struct fsevents_root_state *state = root->watch; unused_parameter(watcher); pthread_mutex_lock(&state->fse_mtx); head = state->fse_head; state->fse_head = NULL; state->fse_tail = NULL; pthread_mutex_unlock(&state->fse_mtx); gettimeofday(&now, 0); while (head) { evt = head; head = head->next; n++; if (evt->flags & kFSEventStreamEventFlagUserDropped) { w_root_schedule_recrawl(root, "kFSEventStreamEventFlagUserDropped"); break_out: w_string_delref(evt->path); free(evt); break; } if (evt->flags & kFSEventStreamEventFlagKernelDropped) { w_root_schedule_recrawl(root, "kFSEventStreamEventFlagKernelDropped"); goto break_out; } if (evt->flags & kFSEventStreamEventFlagUnmount) { w_log(W_LOG_ERR, "kFSEventStreamEventFlagUnmount %.*s, cancel watch\n", evt->path->len, evt->path->buf); w_root_cancel(root); goto break_out; } if (evt->flags & kFSEventStreamEventFlagRootChanged) { w_log(W_LOG_ERR, "kFSEventStreamEventFlagRootChanged %.*s, cancel watch\n", evt->path->len, evt->path->buf); w_root_cancel(root); goto break_out; } recurse = (evt->flags & kFSEventStreamEventFlagMustScanSubDirs) ? true : false; w_root_add_pending(root, evt->path, recurse, now, true); w_string_delref(evt->path); free(evt); } return n > 0; }