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); }
/* 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); }
/* 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 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 /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); }
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-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-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); }
/* 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); }
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); }
/* 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); }
// may attempt to lock the root! bool w_parse_clockspec(w_root_t *root, json_t *value, struct w_clockspec_query *since, bool allow_cursor) { const char *str; int pid; if (json_is_integer(value)) { since->is_timestamp = true; since->tv.tv_usec = 0; since->tv.tv_sec = json_integer_value(value); return true; } str = json_string_value(value); if (!str) { return false; } if (allow_cursor && root && str[0] == 'n' && str[1] == ':') { w_string_t *name = w_string_new(str); since->is_timestamp = false; w_root_lock(root); // If we've never seen it before, ticks will be set to 0 // which is exactly what we want here. since->ticks = (uint32_t)w_ht_get(root->cursors, (w_ht_val_t)name); // Bump the tick value and record it against the cursor. // We need to bump the tick value so that repeated queries // when nothing has changed in the filesystem won't continue // to return the same set of files; we only want the first // of these to return the files and the rest to return nothing // until something subsequently changes w_ht_replace(root->cursors, (w_ht_val_t)name, ++root->ticks); w_log(W_LOG_DBG, "resolved cursor %s -> %" PRIu32 "\n", str, since->ticks); w_string_delref(name); w_root_unlock(root); return true; } if (sscanf(str, "c:%d:%" PRIu32, &pid, &since->ticks) == 2) { since->is_timestamp = false; if (pid == getpid()) { if (root && since->ticks == root->ticks) { /* Force ticks to increment. This avoids returning and querying the * same tick value over and over when no files have changed in the * meantime */ w_root_lock(root); root->ticks++; w_root_unlock(root); } return true; } // If the pid doesn't match, they asked a different // incarnation of the server, so we treat them as having // never spoken to us before since->ticks = 0; return true; } return false; }
bool w_query_execute( w_query *query, w_root_t *root, w_query_res *res, w_query_generator generator, void *gendata) { struct w_query_ctx ctx; memset(&ctx, 0, sizeof(ctx)); ctx.query = query; ctx.root = root; memset(res, 0, sizeof(*res)); if (query->sync_timeout && !w_root_sync_to_now(root, query->sync_timeout)) { ignore_result(asprintf(&res->errmsg, "synchronization failed: %s\n", strerror(errno))); return false; } /* The first stage of execution is generation. * We generate a series of file inputs to pass to * the query executor. * * We evaluate each of the generators one after the * other. If multiple generators are used, it is * possible and expected that the same file name * will be evaluated multiple times if those generators * both emit the same file. */ // Lock the root and begin generation w_root_lock(root); res->root_number = root->number; res->ticks = root->ticks; // Evaluate the cursor for this root w_clockspec_eval(root, query->since_spec, &ctx.since); res->is_fresh_instance = !ctx.since.is_timestamp && ctx.since.clock.is_fresh_instance; if (!(res->is_fresh_instance && query->empty_on_fresh_instance)) { if (!generator) { generator = default_generators; } generator(query, root, &ctx, gendata); } w_root_unlock(root); if (ctx.wholename) { w_string_delref(ctx.wholename); } res->results = ctx.results; res->num_results = ctx.num_results; return true; }
/* 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); }