/* parse an expression term. It can be one of: * "term" * ["term" <parameters>] */ w_query_expr *w_query_expr_parse(w_query *query, json_t *exp) { w_string_t *name; w_query_expr_parser parser; if (json_is_string(exp)) { name = w_string_new(json_string_value(exp)); } else if (json_is_array(exp) && json_array_size(exp) > 0) { json_t *first = json_array_get(exp, 0); if (!json_is_string(first)) { query->errmsg = strdup( "first element of an expression must be a string"); return NULL; } name = w_string_new(json_string_value(first)); } else { query->errmsg = strdup("expected array or string for an expression"); return NULL; } parser = w_ht_val_ptr(w_ht_get(term_hash, w_ht_ptr_val(name))); if (!parser) { ignore_result(asprintf(&query->errmsg, "unknown expression term '%s'", name->buf)); w_string_delref(name); return NULL; } w_string_delref(name); return parser(query, exp); }
static bool parse_relative_root(w_root_t *root, w_query *res, json_t *query) { json_t *relative_root; w_string_t *path, *canon_path; relative_root = json_object_get(query, "relative_root"); if (!relative_root) { return true; } if (!json_is_string(relative_root)) { res->errmsg = strdup("'relative_root' must be a string"); return false; } path = w_string_new(json_string_value(relative_root)); canon_path = w_string_canon_path(path); res->relative_root = w_string_path_cat(root->root_path, canon_path); res->relative_root_slash = w_string_make_printf("%.*s%c", res->relative_root->len, res->relative_root->buf, WATCHMAN_DIR_SEP); w_string_delref(path); w_string_delref(canon_path); return true; }
static bool kqueue_root_start_watch_file(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_file *file) { struct kqueue_root_state *state = root->watch; struct kevent k; w_ht_val_t fdval; int fd; w_string_t *full_name; unused_parameter(watcher); full_name = w_string_path_cat(file->parent->path, file->name); pthread_mutex_lock(&state->lock); if (w_ht_lookup(state->name_to_fd, w_ht_ptr_val(full_name), &fdval, false)) { // Already watching it pthread_mutex_unlock(&state->lock); return true; } pthread_mutex_unlock(&state->lock); w_log(W_LOG_DBG, "watch_file(%s)\n", full_name->buf); fd = open(full_name->buf, O_EVTONLY|O_CLOEXEC); if (fd == -1) { w_log(W_LOG_ERR, "failed to open %s O_EVTONLY: %s\n", full_name->buf, strerror(errno)); w_string_delref(full_name); return false; } memset(&k, 0, sizeof(k)); EV_SET(&k, fd, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_WRITE|NOTE_DELETE|NOTE_EXTEND|NOTE_RENAME|NOTE_ATTRIB, 0, full_name); pthread_mutex_lock(&state->lock); w_ht_replace(state->name_to_fd, w_ht_ptr_val(full_name), fd); w_ht_replace(state->fd_to_name, fd, w_ht_ptr_val(full_name)); pthread_mutex_unlock(&state->lock); if (kevent(state->kq_fd, &k, 1, NULL, 0, 0)) { w_log(W_LOG_DBG, "kevent EV_ADD file %s failed: %s", full_name->buf, strerror(errno)); close(fd); pthread_mutex_lock(&state->lock); w_ht_del(state->name_to_fd, w_ht_ptr_val(full_name)); w_ht_del(state->fd_to_name, fd); pthread_mutex_unlock(&state->lock); } else { w_log(W_LOG_DBG, "kevent file %s -> %d\n", full_name->buf, fd); } w_string_delref(full_name); return true; }
bool dispatch_command(struct watchman_client *client, json_t *args) { watchman_command_func func; const char *cmd_name; w_string_t *cmd; if (!json_array_size(args)) { send_error_response(client, "invalid command (expected an array with some elements!)"); return false; } cmd_name = json_string_value(json_array_get(args, 0)); if (!cmd_name) { send_error_response(client, "invalid command: expected element 0 to be the command name"); return false; } cmd = w_string_new(cmd_name); func = (watchman_command_func)w_ht_get(command_funcs, (w_ht_val_t)cmd); w_string_delref(cmd); if (func) { func(client, args); return true; } send_error_response(client, "unknown command %s", cmd_name); return false; }
bool w_capability_supported(const char *name) { bool res; w_string_t *namestr = w_string_new(name); res = w_ht_get(capabilities, w_ht_ptr_val(namestr)); w_string_delref(namestr); return res; }
static bool winwatch_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct winwatch_root_state *state = root->watch; struct winwatch_changed_item *head, *item; struct timeval now; int n = 0; unused_parameter(watcher); pthread_mutex_lock(&state->mtx); head = state->head; state->head = NULL; state->tail = NULL; pthread_mutex_unlock(&state->mtx); gettimeofday(&now, NULL); while (head) { item = head; head = head->next; n++; w_log(W_LOG_DBG, "readchanges: add pending %.*s\n", item->name->len, item->name->buf); w_pending_coll_add(coll, item->name, now, W_PENDING_VIA_NOTIFY); w_string_delref(item->name); free(item); } return n > 0; }
void fsevents_root_dtor(watchman_global_watcher_t watcher, w_root_t *root) { struct fsevents_root_state *state = root->watch; unused_parameter(watcher); if (!state) { return; } // wait for fsevents thread to quit if (!pthread_equal(state->fse_thread, pthread_self())) { void *ignore; pthread_join(state->fse_thread, &ignore); } pthread_cond_destroy(&state->fse_cond); pthread_mutex_destroy(&state->fse_mtx); close(state->fse_pipe[0]); close(state->fse_pipe[1]); while (state->fse_head) { struct watchman_fsevent *evt = state->fse_head; state->fse_head = evt->next; w_string_delref(evt->path); free(evt); } free(state); root->watch = NULL; }
void w_query_delref(w_query *query) { uint32_t i; if (!w_refcnt_del(&query->refcnt)) { return; } for (i = 0; i < query->npaths; i++) { if (query->paths[i].name) { w_string_delref(query->paths[i].name); } } free(query->paths); if (query->since_spec) { w_clockspec_free(query->since_spec); } if (query->expr) { w_query_expr_delref(query->expr); } free(query); }
w_string_t *w_query_ctx_get_wholename( struct w_query_ctx *ctx ) { w_string_t *full_name; uint32_t name_start; if (ctx->wholename) { return ctx->wholename; } if (ctx->query->relative_root != NULL) { // At this point every path should start with the relative root, so this is // legal name_start = ctx->query->relative_root->len + 1; } else { name_start = ctx->root->root_path->len + 1; } full_name = w_string_path_cat(compute_parent_path(ctx, ctx->file), w_file_get_name(ctx->file)); // Record the name relative to the root ctx->wholename = w_string_slice(full_name, name_start, full_name->len - name_start); w_string_delref(full_name); return ctx->wholename; }
void w_clockspec_free(struct w_clockspec *spec) { if (spec->tag == w_cs_named_cursor) { w_string_delref(spec->named_cursor.cursor); } free(spec); }
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 void dispose_dirname(void *ptr) { struct dirname_data *data = ptr; if (data->dirname) { w_string_delref(data->dirname); } free(data); }
static void delete_subscription(w_ht_val_t val) { struct watchman_client_subscription *sub = w_ht_val_ptr(val); w_string_delref(sub->name); w_query_delref(sub->query); free(sub); }
void w_perf_destroy(w_perf_t *perf) { if (perf->root_path) { w_string_delref(perf->root_path); } if (perf->meta_data) { json_decref(perf->meta_data); } memset(perf, 0, sizeof(*perf)); }
void w_match_results_free(uint32_t num_matches, struct watchman_rule_match *matches) { uint32_t i; for (i = 0; i < num_matches; i++) { w_string_delref(matches[i].relname); } free(matches); }
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 void dispose_name(void *data) { struct name_data *name = data; if (name->map) { w_ht_free(name->map); } if (name->name) { w_string_delref(name->name); } free(name); }
static bool parse_paths(w_query *res, json_t *query) { json_t *paths; size_t i; paths = json_object_get(query, "path"); if (!paths) { return true; } if (!json_is_array(paths)) { res->errmsg = strdup("'path' must be an array"); return false; } res->npaths = json_array_size(paths); res->paths = calloc(res->npaths, sizeof(res->paths[0])); if (!res->paths) { res->errmsg = strdup("out of memory"); return false; } for (i = 0; i < json_array_size(paths); i++) { json_t *ele = json_array_get(paths, i); const char *name = NULL; w_string_t *path; res->paths[i].depth = -1; if (json_is_string(ele)) { name = json_string_value(ele); } else if (json_unpack(ele, "{s:s, s:i}", "path", &name, "depth", &res->paths[i].depth ) != 0) { res->errmsg = strdup( "expected object with 'path' and 'depth' properties" ); return false; } path = w_string_new(name); res->paths[i].name = w_string_canon_path(path); w_string_delref(path); } return true; }
static w_string_t *compute_parent_path(struct w_query_ctx *ctx, struct watchman_file *file) { if (ctx->last_parent == file->parent) { return ctx->last_parent_path; } if (ctx->last_parent_path) { w_string_delref(ctx->last_parent_path); } ctx->last_parent_path = w_dir_copy_full_path(file->parent); ctx->last_parent = file->parent; return ctx->last_parent_path; }
w_string& w_string::operator=(const w_string& other) { if (&other == this) { return *this; } reset(); if (str_) { w_string_delref(str_); } str_ = other.str_; if (str_) { w_string_addref(str_); } return *this; }
/* trigger-del /root triggername * Delete a trigger from a root */ static void cmd_trigger_delete(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; json_t *jname; w_string_t *tname; bool res; root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } if (json_array_size(args) != 3) { send_error_response(client, "wrong number of arguments"); w_root_delref(root); return; } jname = json_array_get(args, 2); if (!json_is_string(jname)) { send_error_response(client, "expected 2nd parameter to be trigger name"); w_root_delref(root); return; } tname = json_to_w_string_incref(jname); w_root_lock(root, "trigger-del"); res = w_ht_del(root->commands, w_ht_ptr_val(tname)); w_root_unlock(root); if (res) { w_state_save(); } w_string_delref(tname); resp = make_response(); set_prop(resp, "deleted", json_boolean(res)); json_incref(jname); set_prop(resp, "trigger", jname); send_and_dispose_response(client, resp); w_root_delref(root); }
w_string_t *w_query_ctx_get_wholename( struct w_query_ctx *ctx ) { w_string_t *full_name; uint32_t name_start; if (ctx->wholename) { return ctx->wholename; } name_start = ctx->root->root_path->len + 1; full_name = w_string_path_cat(ctx->file->parent->path, ctx->file->name); // Record the name relative to the root ctx->wholename = w_string_slice(full_name, name_start, full_name->len - name_start); w_string_delref(full_name); return ctx->wholename; }
/* trigger-del /root triggername * Delete a trigger from a root */ void cmd_trigger_delete(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; const char *name; w_string_t *tname; bool res; root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } if (json_array_size(args) != 3) { send_error_response(client, "wrong number of arguments"); w_root_delref(root); return; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be trigger name"); w_root_delref(root); return; } tname = w_string_new(name); w_root_lock(root); res = w_ht_del(root->commands, (w_ht_val_t)tname); w_root_unlock(root); w_state_save(); w_string_delref(tname); resp = make_response(); set_prop(resp, "deleted", json_boolean(res)); set_prop(resp, "trigger", json_string_nocheck(name)); send_and_dispose_response(client, resp); w_root_delref(root); }
/* unsubscribe /root subname * Cancels a subscription */ static void cmd_unsubscribe(struct watchman_client *clientbase, json_t *args) { w_root_t *root; const char *name; w_string_t *sname; bool deleted; json_t *resp; const json_t *jstr; struct watchman_user_client *client = (struct watchman_user_client *)clientbase; root = resolve_root_or_err(&client->client, args, 1, false); if (!root) { return; } jstr = json_array_get(args, 2); name = json_string_value(jstr); if (!name) { send_error_response(&client->client, "expected 2nd parameter to be subscription name"); w_root_delref(root); return; } sname = json_to_w_string_incref(jstr); pthread_mutex_lock(&w_client_lock); deleted = w_ht_del(client->subscriptions, w_ht_ptr_val(sname)); pthread_mutex_unlock(&w_client_lock); w_string_delref(sname); resp = make_response(); set_bytestring_prop(resp, "unsubscribe", name); set_prop(resp, "deleted", json_boolean(deleted)); send_and_dispose_response(&client->client, resp); w_root_delref(root); }
static struct watchman_command_handler_def *lookup( json_t *args, char **errmsg, int mode) { struct watchman_command_handler_def *def; const char *cmd_name; w_string_t *cmd; if (!json_array_size(args)) { ignore_result(asprintf(errmsg, "invalid command (expected an array with some elements!)")); return false; } cmd_name = json_string_value(json_array_get(args, 0)); if (!cmd_name) { ignore_result(asprintf(errmsg, "invalid command: expected element 0 to be the command name")); return false; } cmd = w_string_new(cmd_name); def = w_ht_val_ptr(w_ht_get(command_funcs, w_ht_ptr_val(cmd))); w_string_delref(cmd); if (def) { if (mode && ((def->flags & mode) == 0)) { ignore_result(asprintf(errmsg, "command %s not available in this mode", cmd_name)); return NULL; } return def; } if (mode) { ignore_result(asprintf(errmsg, "unknown command %s", cmd_name)); } return NULL; }
static bool eval_name(struct w_query_ctx *ctx, struct watchman_file *file, void *data) { struct name_data *name = data; w_string_t *str; if (name->wholename) { str = w_query_ctx_get_wholename(ctx); } else { str = file->name; } if (name->map) { bool matched; w_ht_val_t val; if (name->caseless) { str = w_string_dup_lower(str); if (!str) { return false; } } matched = w_ht_lookup(name->map, w_ht_ptr_val(str), &val, false); if (name->caseless) { w_string_delref(str); } return matched; } if (name->caseless) { return w_string_equal_caseless(str, name->name); } return w_string_equal(str, name->name); }
void w_trigger_command_free(struct watchman_trigger_command *cmd) { if (cmd->triggername) { w_string_delref(cmd->triggername); } if (cmd->command) { json_decref(cmd->command); } if (cmd->definition) { json_decref(cmd->definition); } if (cmd->query) { w_query_delref(cmd->query); } if (cmd->envht) { w_ht_free(cmd->envht); } free(cmd); }
void w_string_delref(w_string_t *str) { if (!w_refcnt_del(&str->refcnt)) return; if (str->slice) w_string_delref(str->slice); free(str); }
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; }