/* find /root [patterns] */ static void cmd_find(struct watchman_client *client, json_t *args) { w_root_t *root; w_query *query; char *errmsg = NULL; struct w_query_field_list field_list; w_query_res res; json_t *response; json_t *file_list; char clockbuf[128]; /* resolve the root */ if (json_array_size(args) < 2) { send_error_response(client, "not enough arguments for 'find'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } query = w_query_parse_legacy(root, args, &errmsg, 2, NULL, NULL, NULL); if (errmsg) { send_error_response(client, "%s", errmsg); free(errmsg); w_root_delref(root); return; } w_query_legacy_field_list(&field_list); if (client->client_mode) { query->sync_timeout = 0; } if (!w_query_execute(query, root, &res, NULL, NULL)) { send_error_response(client, "query failed: %s", res.errmsg); w_query_result_free(&res); w_root_delref(root); w_query_delref(query); return; } w_query_delref(query); file_list = w_query_results_to_json(&field_list, res.num_results, res.results); w_query_result_free(&res); response = make_response(); if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "clock", json_string_nocheck(clockbuf)); } set_prop(response, "files", file_list); send_and_dispose_response(client, response); w_root_delref(root); }
static void cmd_debug_recrawl(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; /* resolve the root */ if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments for 'debug-recrawl'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); w_root_lock(root); w_root_schedule_recrawl(root, "debug-recrawl"); w_root_unlock(root); set_prop(resp, "recrawl", json_true()); send_and_dispose_response(client, resp); w_root_delref(root); }
/* debug-ageout */ static void cmd_debug_ageout(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; int min_age; /* resolve the root */ if (json_array_size(args) != 3) { send_error_response(client, "wrong number of arguments for 'debug-ageout'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } min_age = json_integer_value(json_array_get(args, 2)); resp = make_response(); w_root_lock(root); w_root_perform_age_out(root, min_age); w_root_unlock(root); set_prop(resp, "ageout", json_true()); send_and_dispose_response(client, resp); w_root_delref(root); }
static bool winwatch_root_start(watchman_global_watcher_t watcher, w_root_t *root) { struct winwatch_root_state *state = root->watch; int err; unused_parameter(watcher); unused_parameter(root); // Spin up the changes reading thread; it owns a ref on the root w_root_addref(root); // Acquire the mutex so thread initialization waits until we release it pthread_mutex_lock(&state->mtx); err = pthread_create(&state->thread, NULL, readchanges_thread, root); if (err == 0) { // Allow thread init to proceed; wait for its signal pthread_cond_wait(&state->cond, &state->mtx); pthread_mutex_unlock(&state->mtx); if (root->failure_reason) { w_log(W_LOG_ERR, "failed to start readchanges thread: %.*s\n", root->failure_reason->len, root->failure_reason->buf); return false; } return true; } pthread_mutex_unlock(&state->mtx); w_root_delref(root); w_log(W_LOG_ERR, "failed to start readchanges thread: %s\n", strerror(err)); return false; }
/* watch /root */ static void cmd_watch(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; /* resolve the root */ if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments to 'watch'"); return; } root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } resp = make_response(); w_root_lock(root); if (root->failure_reason) { set_prop(resp, "error", json_string_nocheck(root->failure_reason->buf)); } else if (root->cancelled) { set_prop(resp, "error", json_string_nocheck("root was cancelled")); } else { set_prop(resp, "watch", json_string_nocheck(root->root_path->buf)); } send_and_dispose_response(client, resp); w_root_unlock(root); w_root_delref(root); }
static void cmd_debug_show_cursors(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp, *cursors; w_ht_iter_t i; /* resolve the root */ if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments for 'debug-show-cursors'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); w_root_lock(root); cursors = json_object_of_size(w_ht_size(root->cursors)); if (w_ht_first(root->cursors, &i)) do { w_string_t *name = w_ht_val_ptr(i.key); set_prop(cursors, name->buf, json_integer(i.value)); } while (w_ht_next(root->cursors, &i)); w_root_unlock(root); set_prop(resp, "cursors", cursors); send_and_dispose_response(client, resp); w_root_delref(root); }
/* 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); }
/* trigger /root triggername [watch patterns] -- cmd to run * Sets up a trigger so that we can execute a command when a change * is detected */ void cmd_trigger(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_trigger_command *cmd; json_t *resp; json_t *trig; char *errmsg = NULL; root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } if (json_array_size(args) < 3) { send_error_response(client, "not enough arguments"); goto done; } trig = json_array_get(args, 2); if (json_is_string(trig)) { trig = build_legacy_trigger(client, args); if (!trig) { goto done; } } else { // Add a ref so that we don't need to conditionally decref later // for the legacy case later json_incref(trig); } cmd = w_build_trigger_from_def(root, trig, &errmsg); json_decref(trig); if (!cmd) { send_error_response(client, "%s", errmsg); goto done; } w_root_lock(root); w_ht_replace(root->commands, w_ht_ptr_val(cmd->triggername), w_ht_ptr_val(cmd)); w_root_unlock(root); w_state_save(); resp = make_response(); set_prop(resp, "triggerid", json_string_nocheck(cmd->triggername->buf)); send_and_dispose_response(client, resp); done: if (errmsg) { free(errmsg); } w_root_delref(root); }
/* 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); }
void w_mark_dead(pid_t pid) { w_root_t *root = NULL; w_ht_iter_t iter; pthread_mutex_lock(&spawn_lock); root = lookup_running_pid(pid); if (!root) { pthread_mutex_unlock(&spawn_lock); return; } delete_running_pid(pid); pthread_mutex_unlock(&spawn_lock); w_log(W_LOG_DBG, "mark_dead: %.*s child pid %d\n", root->root_path->len, root->root_path->buf, (int)pid); /* now walk the cmds and try to find our match */ w_root_lock(root); /* walk the list of triggers, and run their rules */ if (w_ht_first(root->commands, &iter)) do { struct watchman_trigger_command *cmd; cmd = w_ht_val_ptr(iter.value); if (cmd->current_proc != pid) { w_log(W_LOG_DBG, "mark_dead: is [%.*s] %d == %d\n", cmd->triggername->len, cmd->triggername->buf, (int)cmd->current_proc, (int)pid); continue; } /* first mark the process as dead */ cmd->current_proc = 0; if (root->cancelled) { w_log(W_LOG_DBG, "mark_dead: root was cancelled\n"); break; } w_assess_trigger(root, cmd); break; } while (w_ht_next(root->commands, &iter)); w_root_unlock(root); w_root_delref(root); }
/* trigger-list /root * Displays a list of registered triggers for a given root */ void cmd_trigger_list(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; json_t *arr; root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); w_root_lock(root); arr = w_root_trigger_list_to_json(root); w_root_unlock(root); set_prop(resp, "triggers", arr); send_and_dispose_response(client, resp); w_root_delref(root); }
/* watch-del /root * Stops watching the specified root */ static void cmd_watch_delete(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; /* resolve the root */ if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments to 'watch-del'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); set_prop(resp, "watch-del", json_boolean(w_root_stop_watch(root))); set_prop(resp, "root", json_string_nocheck(root->root_path->buf)); send_and_dispose_response(client, resp); w_root_delref(root); }
static void cmd_get_config(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; json_t *config; if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments for 'get-config'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); w_root_lock(root); { config = root->config_file; if (config) { // set_prop will claim this ref json_incref(config); } } w_root_unlock(root); if (!config) { // set_prop will own this config = json_object(); } json_incref(root->config_file); set_prop(resp, "config", config); send_and_dispose_response(client, resp); w_root_delref(root); }
static void cmd_debug_poison(struct watchman_client *client, json_t *args) { w_root_t *root; struct timeval now; struct watchman_dir *dir; json_t *resp; root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } dir = w_root_resolve_dir(root, root->root_path, false); gettimeofday(&now, NULL); set_poison_state(root, dir, now, "debug-poison", ENOMEM); resp = make_response(); set_prop(resp, "poison", json_string_nocheck(poisoned_reason)); send_and_dispose_response(client, resp); w_root_delref(root); }
/* clock /root * Returns the current clock value for a watched root */ static void cmd_clock(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; /* resolve the root */ if (json_array_size(args) != 2) { send_error_response(client, "wrong number of arguments to 'clock'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } resp = make_response(); w_root_lock(root); annotate_with_clock(root, resp); w_root_unlock(root); send_and_dispose_response(client, resp); w_root_delref(root); }
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; }
/* trigger /root triggername [watch patterns] -- cmd to run * Sets up a trigger so that we can execute a command when a change * is detected */ static void cmd_trigger(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_trigger_command *cmd, *old; json_t *resp; json_t *trig; char *errmsg = NULL; bool need_save = true; root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } if (json_array_size(args) < 3) { send_error_response(client, "not enough arguments"); goto done; } trig = json_array_get(args, 2); if (json_is_string(trig)) { trig = build_legacy_trigger(root, client, args); if (!trig) { goto done; } } else { // Add a ref so that we don't need to conditionally decref later // for the legacy case later json_incref(trig); } cmd = w_build_trigger_from_def(root, trig, &errmsg); json_decref(trig); if (!cmd) { send_error_response(client, "%s", errmsg); goto done; } resp = make_response(); set_prop(resp, "triggerid", json_string_nocheck(cmd->triggername->buf)); w_root_lock(root); old = w_ht_val_ptr(w_ht_get(root->commands, w_ht_ptr_val(cmd->triggername))); if (old && json_equal(cmd->definition, old->definition)) { // Same definition: we don't and shouldn't touch things, so that we // preserve the associated trigger clock and don't cause the trigger // to re-run immediately set_prop(resp, "disposition", json_string_nocheck("already_defined")); w_trigger_command_free(cmd); cmd = NULL; need_save = false; } else { set_prop(resp, "disposition", json_string_nocheck( old ? "replaced" : "created")); w_ht_replace(root->commands, w_ht_ptr_val(cmd->triggername), w_ht_ptr_val(cmd)); // Force the trigger to be eligible to run now root->ticks++; root->pending_trigger_tick = root->ticks; } w_root_unlock(root); if (need_save) { w_state_save(); } send_and_dispose_response(client, resp); done: if (errmsg) { free(errmsg); } w_root_delref(root); }
/* trigger /root triggername [watch patterns] -- cmd to run * Sets up a trigger so that we can execute a command when a change * is detected */ void cmd_trigger(struct watchman_client *client, json_t *args) { struct watchman_rule *rules; w_root_t *root; uint32_t next_arg = 0; struct watchman_trigger_command *cmd; json_t *resp; const char *name; char buf[128]; root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } if (json_array_size(args) < 2) { send_error_response(client, "not enough arguments"); goto done; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be trigger name"); goto done; } if (!parse_watch_params(3, args, &rules, &next_arg, buf, sizeof(buf))) { send_error_response(client, "invalid rule spec: %s", buf); goto done; } if (next_arg >= json_array_size(args)) { send_error_response(client, "no command was specified"); goto done; } cmd = calloc(1, sizeof(*cmd)); if (!cmd) { send_error_response(client, "no memory!"); goto done; } cmd->rules = rules; cmd->argc = json_array_size(args) - next_arg; cmd->argv = w_argv_copy_from_json(args, next_arg); if (!cmd->argv) { free(cmd); send_error_response(client, "unable to build argv array"); goto done; } cmd->triggername = w_string_new(name); w_root_lock(root); w_ht_replace(root->commands, (w_ht_val_t)cmd->triggername, (w_ht_val_t)cmd); w_root_unlock(root); w_state_save(); resp = make_response(); set_prop(resp, "triggerid", json_string_nocheck(name)); send_and_dispose_response(client, resp); done: w_root_delref(root); }
/* query /root {query} */ static void cmd_query(struct watchman_client *client, json_t *args) { w_root_t *root; w_query *query; json_t *query_spec; char *errmsg = NULL; w_query_res res; json_t *response; json_t *file_list, *jfield_list; char clockbuf[128]; struct w_query_field_list field_list; if (json_array_size(args) != 3) { send_error_response(client, "wrong number of arguments for 'query'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } query_spec = json_array_get(args, 2); jfield_list = json_object_get(query_spec, "fields"); if (!parse_field_list(jfield_list, &field_list, &errmsg)) { send_error_response(client, "invalid field list: %s", errmsg); free(errmsg); w_root_delref(root); return; } query = w_query_parse(root, query_spec, &errmsg); if (!query) { send_error_response(client, "failed to parse query: %s", errmsg); free(errmsg); w_root_delref(root); return; } if (client->client_mode) { query->sync_timeout = 0; } if (!w_query_execute(query, root, &res, NULL, NULL)) { send_error_response(client, "query failed: %s", res.errmsg); w_query_result_free(&res); w_root_delref(root); w_query_delref(query); return; } w_query_delref(query); file_list = w_query_results_to_json(&field_list, res.num_results, res.results); w_query_result_free(&res); response = make_response(); if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "clock", json_string_nocheck(clockbuf)); } set_prop(response, "is_fresh_instance", json_pack("b", res.is_fresh_instance)); set_prop(response, "files", file_list); send_and_dispose_response(client, response); w_root_delref(root); }
/* since /root <timestamp> [patterns] */ static void cmd_since(struct watchman_client *client, json_t *args) { const char *clockspec; w_root_t *root; w_query *query; char *errmsg = NULL; struct w_query_field_list field_list; w_query_res res; json_t *response, *clock_ele; json_t *file_list; char clockbuf[128]; /* resolve the root */ if (json_array_size(args) < 3) { send_error_response(client, "not enough arguments for 'since'"); return; } root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } clock_ele = json_array_get(args, 2); clockspec = json_string_value(clock_ele); if (!clockspec) { send_error_response(client, "expected argument 2 to be a valid clockspec"); w_root_delref(root); return; } query = w_query_parse_legacy(args, &errmsg, 3, NULL, clockspec, NULL); if (errmsg) { send_error_response(client, "%s", errmsg); free(errmsg); w_root_delref(root); return; } w_query_legacy_field_list(&field_list); if (!w_query_execute(query, root, &res, NULL, NULL)) { send_error_response(client, "query failed: %s", res.errmsg); w_query_result_free(&res); w_root_delref(root); w_query_delref(query); return; } w_query_delref(query); file_list = w_query_results_to_json(&field_list, res.num_results, res.results); w_query_result_free(&res); response = make_response(); if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "clock", json_string_nocheck(clockbuf)); } set_prop(response, "is_fresh_instance", json_pack("b", res.is_fresh_instance)); set_prop(response, "files", file_list); send_and_dispose_response(client, response); w_root_delref(root); }
static void *fsevents_thread(void *arg) { w_root_t *root = arg; FSEventStreamContext ctx; CFMutableArrayRef parray; CFStringRef cpath; FSEventStreamRef fs_stream = NULL; CFFileDescriptorContext fdctx; CFFileDescriptorRef fdref; struct fsevents_root_state *state = root->watch; double latency; w_set_thread_name("fsevents %.*s", root->root_path->len, root->root_path->buf); // Block until fsevents_root_start is waiting for our initialization pthread_mutex_lock(&state->fse_mtx); memset(&ctx, 0, sizeof(ctx)); ctx.info = root; memset(&fdctx, 0, sizeof(fdctx)); fdctx.info = root; fdref = CFFileDescriptorCreate(NULL, state->fse_pipe[0], true, fse_pipe_callback, &fdctx); CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); { CFRunLoopSourceRef fdsrc; fdsrc = CFFileDescriptorCreateRunLoopSource(NULL, fdref, 0); if (!fdsrc) { root->failure_reason = w_string_new( "CFFileDescriptorCreateRunLoopSource failed"); goto done; } CFRunLoopAddSource(CFRunLoopGetCurrent(), fdsrc, kCFRunLoopDefaultMode); CFRelease(fdsrc); } parray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (!parray) { root->failure_reason = w_string_new("CFArrayCreateMutable failed"); goto done; } cpath = CFStringCreateWithBytes(NULL, (const UInt8*)root->root_path->buf, root->root_path->len, kCFStringEncodingUTF8, false); if (!cpath) { root->failure_reason = w_string_new("CFStringCreateWithBytes failed"); goto done; } CFArrayAppendValue(parray, cpath); CFRelease(cpath); latency = cfg_get_double(root, "fsevents_latency", 0.01), w_log(W_LOG_DBG, "FSEventStreamCreate for path %.*s with latency %f seconds\n", root->root_path->len, root->root_path->buf, latency); fs_stream = FSEventStreamCreate(NULL, fse_callback, &ctx, parray, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNoDefer| kFSEventStreamCreateFlagWatchRoot| kFSEventStreamCreateFlagFileEvents); if (!fs_stream) { root->failure_reason = w_string_new("FSEventStreamCreate failed"); goto done; } FSEventStreamScheduleWithRunLoop(fs_stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); if (!FSEventStreamStart(fs_stream)) { root->failure_reason = w_string_make_printf( "FSEventStreamStart failed, look at your log file %s for " "lines mentioning FSEvents and see " "https://facebook.github.io/watchman/docs/troubleshooting.html#" "fsevents for more information\n", log_name); goto done; } // Signal to fsevents_root_start that we're done initializing pthread_cond_signal(&state->fse_cond); pthread_mutex_unlock(&state->fse_mtx); // Process the events stream until we get signalled to quit CFRunLoopRun(); // Since the goto's above hold fse_mtx, we should grab it here pthread_mutex_lock(&state->fse_mtx); done: if (fs_stream) { FSEventStreamStop(fs_stream); FSEventStreamInvalidate(fs_stream); FSEventStreamRelease(fs_stream); } if (fdref) { CFRelease(fdref); } // Signal to fsevents_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. fsevents_root_dtor // will `pthread_join` us before `state` is freed. pthread_cond_signal(&state->fse_cond); pthread_mutex_unlock(&state->fse_mtx); w_log(W_LOG_DBG, "fse_thread done\n"); w_root_delref(root); return NULL; }
/* subscribe /root subname {query} * Subscribes the client connection to the specified root. */ void cmd_subscribe(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_client_subscription *sub; json_t *resp; const char *name; json_t *jfield_list; w_query *query; json_t *query_spec; struct w_query_field_list field_list; char *errmsg; if (json_array_size(args) != 4) { send_error_response(client, "wrong number of arguments for subscribe"); return; } root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be subscription name"); goto done; } query_spec = json_array_get(args, 3); jfield_list = json_object_get(query_spec, "fields"); if (!parse_field_list(jfield_list, &field_list, &errmsg)) { send_error_response(client, "invalid field list: %s", errmsg); free(errmsg); goto done; } query = w_query_parse(query_spec, &errmsg); if (!query) { send_error_response(client, "failed to parse query: %s", errmsg); free(errmsg); goto done; } sub = calloc(1, sizeof(*sub)); if (!sub) { send_error_response(client, "no memory!"); goto done; } sub->name = w_string_new(name); sub->query = query; memcpy(&sub->field_list, &field_list, sizeof(field_list)); sub->root = root; pthread_mutex_lock(&w_client_lock); w_ht_replace(client->subscriptions, w_ht_ptr_val(sub->name), w_ht_ptr_val(sub)); pthread_mutex_unlock(&w_client_lock); resp = make_response(); annotate_with_clock(root, resp); set_prop(resp, "subscribe", json_string(name)); send_and_dispose_response(client, resp); resp = build_subscription_results(sub, root); if (resp) { send_and_dispose_response(client, resp); } done: w_root_delref(root); }
/* subscribe /root subname {query} * Subscribes the client connection to the specified root. */ static void cmd_subscribe(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_client_subscription *sub; json_t *resp; const char *name; json_t *jfield_list; w_query *query; json_t *query_spec; struct w_query_field_list field_list; char *errmsg; int defer = true; /* can't use bool because json_unpack requires int */ json_t *defer_list = NULL; json_t *drop_list = NULL; if (json_array_size(args) != 4) { send_error_response(client, "wrong number of arguments for subscribe"); return; } root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be subscription name"); goto done; } query_spec = json_array_get(args, 3); jfield_list = json_object_get(query_spec, "fields"); if (!parse_field_list(jfield_list, &field_list, &errmsg)) { send_error_response(client, "invalid field list: %s", errmsg); free(errmsg); goto done; } query = w_query_parse(root, query_spec, &errmsg); if (!query) { send_error_response(client, "failed to parse query: %s", errmsg); free(errmsg); goto done; } json_unpack(query_spec, "{s?:o}", "defer", &defer_list); if (defer_list && !json_is_array(defer_list)) { send_error_response(client, "defer field must be an array of strings"); goto done; } json_unpack(query_spec, "{s?:o}", "drop", &drop_list); if (drop_list && !json_is_array(drop_list)) { send_error_response(client, "drop field must be an array of strings"); goto done; } sub = calloc(1, sizeof(*sub)); if (!sub) { send_error_response(client, "no memory!"); goto done; } sub->name = w_string_new(name); sub->query = query; json_unpack(query_spec, "{s?:b}", "defer_vcs", &defer); sub->vcs_defer = defer; if (drop_list || defer_list) { size_t i; sub->drop_or_defer = w_ht_new(2, &w_ht_string_funcs); if (defer_list) { for (i = 0; i < json_array_size(defer_list); i++) { w_ht_replace(sub->drop_or_defer, w_ht_ptr_val(w_string_new(json_string_value( json_array_get(defer_list, i)))), false); } } if (drop_list) { for (i = 0; i < json_array_size(drop_list); i++) { w_ht_replace(sub->drop_or_defer, w_ht_ptr_val(w_string_new(json_string_value( json_array_get(drop_list, i)))), true); } } } memcpy(&sub->field_list, &field_list, sizeof(field_list)); sub->root = root; pthread_mutex_lock(&w_client_lock); w_ht_replace(client->subscriptions, w_ht_ptr_val(sub->name), w_ht_ptr_val(sub)); pthread_mutex_unlock(&w_client_lock); resp = make_response(); annotate_with_clock(root, resp); set_prop(resp, "subscribe", json_string(name)); add_root_warnings_to_response(resp, root); send_and_dispose_response(client, resp); resp = build_subscription_results(sub, root); if (resp) { send_and_dispose_response(client, resp); } done: w_root_delref(root); }