static json_t *build_subscription_results( struct watchman_client_subscription *sub, w_root_t *root) { w_query_res res; json_t *response; json_t *file_list; char clockbuf[128]; w_log(W_LOG_DBG, "running subscription rules! since %" PRIu32 "\n", sub->since.ticks); // Subscriptions never need to sync explicitly; we are only dispatched // at settle points which are by definition sync'd to the present time sub->query->sync_timeout = 0; if (!w_query_execute(sub->query, root, &res, subscription_generator, sub)) { w_log(W_LOG_ERR, "error running subscription query: %s", res.errmsg); w_query_result_free(&res); return NULL; } w_log(W_LOG_DBG, "subscription generated %" PRIu32 " results\n", res.num_results); file_list = w_query_results_to_json(&sub->field_list, res.num_results, res.results); w_query_result_free(&res); if (res.num_results == 0) { return NULL; } response = make_response(); if (clock_id_string(sub->since.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "since", json_string_nocheck(clockbuf)); } if (clock_id_string(res.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "clock", json_string_nocheck(clockbuf)); } sub->since.is_timestamp = false; sub->since.ticks = res.ticks; set_prop(response, "files", file_list); set_prop(response, "root", json_string(root->root_path->buf)); set_prop(response, "subscription", json_string(sub->name->buf)); return response; }
/* 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); }
// Renders the current clock id string to the supplied buffer. // Must be called with the root locked. static bool current_clock_id_string(w_root_t *root, char *buf, size_t bufsize) { return clock_id_string(root->number, root->ticks, buf, bufsize); }
json_t *w_match_results_to_json( uint32_t num_matches, struct watchman_rule_match *matches) { json_t *file_list = json_array_of_size(num_matches); uint32_t i; if (num_matches) { // Build a template for the serializer json_t *templ = json_array_of_size(15); static const char *field_list[] = { "name", "exists", "size", "mode", "uid", "gid", "mtime", "ctime", "ino", "dev", "nlink", "new", "oclock", "cclock", }; for (i = 0; i < sizeof(field_list)/sizeof(field_list[0]); i++) { json_array_append_new(templ, json_string_nocheck(field_list[i])); } json_array_set_template_new(file_list, templ); } for (i = 0; i < num_matches; i++) { struct watchman_file *file = matches[i].file; w_string_t *relname = matches[i].relname; char buf[128]; json_t *record = json_object_of_size(15); set_prop(record, "name", json_string_nocheck(relname->buf)); set_prop(record, "exists", json_boolean(file->exists)); // Only report stat data if we think this file exists. If it doesn't, // we probably have stale data cached in file->st which is useless to // report on. if (file->exists) { // Note: our JSON library supports 64-bit integers, but this may // pose a compatibility issue for others. We'll see if anyone // runs into an issue and deal with it then... set_prop(record, "size", json_integer(file->st.st_size)); set_prop(record, "mode", json_integer(file->st.st_mode)); set_prop(record, "uid", json_integer(file->st.st_uid)); set_prop(record, "gid", json_integer(file->st.st_gid)); set_prop(record, "mtime", json_integer(file->st.st_mtime)); set_prop(record, "ctime", json_integer(file->st.st_ctime)); set_prop(record, "ino", json_integer(file->st.st_ino)); set_prop(record, "dev", json_integer(file->st.st_dev)); set_prop(record, "nlink", json_integer(file->st.st_nlink)); set_prop(record, "new", json_boolean(matches[i].is_new)); if (clock_id_string(matches[i].root_number, file->ctime.ticks, buf, sizeof(buf))) { set_prop(record, "cclock", json_string_nocheck(buf)); } } if (clock_id_string(matches[i].root_number, file->otime.ticks, buf, sizeof(buf))) { set_prop(record, "oclock", json_string_nocheck(buf)); } json_array_append_new(file_list, record); } return file_list; }
static json_t *build_subscription_results( struct watchman_client_subscription *sub, w_root_t *root) { w_query_res res; json_t *response; json_t *file_list; char clockbuf[128]; struct w_clockspec *since_spec = sub->query->since_spec; if (since_spec && since_spec->tag == w_cs_clock) { w_log(W_LOG_DBG, "running subscription rules! since %" PRIu32 "\n", since_spec->clock.ticks); } else { w_log(W_LOG_DBG, "running subscription rules!\n"); } // Subscriptions never need to sync explicitly; we are only dispatched // at settle points which are by definition sync'd to the present time sub->query->sync_timeout = 0; if (!w_query_execute(sub->query, root, &res, subscription_generator, sub)) { w_log(W_LOG_ERR, "error running subscription query: %s", res.errmsg); w_query_result_free(&res); return NULL; } w_log(W_LOG_DBG, "subscription generated %" PRIu32 " results\n", res.num_results); if (res.num_results == 0) { w_query_result_free(&res); return NULL; } file_list = w_query_results_to_json(&sub->field_list, res.num_results, res.results); w_query_result_free(&res); response = make_response(); // It is way too much of a hassle to try to recreate the clock value if it's // not a relative clock spec, and it's only going to happen on the first run // anyway, so just skip doing that entirely. if (since_spec && since_spec->tag == w_cs_clock && clock_id_string(since_spec->clock.root_number, since_spec->clock.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "since", json_string_nocheck(clockbuf)); } if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) { set_prop(response, "clock", json_string_nocheck(clockbuf)); } // create a new spec that will be used the next time if (since_spec) { w_clockspec_free(since_spec); since_spec = NULL; } sub->query->since_spec = w_clockspec_new_clock(res.root_number, res.ticks); set_prop(response, "is_fresh_instance", json_boolean(res.is_fresh_instance)); set_prop(response, "files", file_list); set_prop(response, "root", json_string(root->root_path->buf)); set_prop(response, "subscription", json_string(sub->name->buf)); return response; }
/* 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); }
static void spawn_command(w_root_t *root, struct watchman_trigger_command *cmd, w_query_res *res, struct w_clockspec *since_spec) { char **envp = NULL; uint32_t i = 0; int ret; int stdin_fd = -1; json_t *args; char **argv = NULL; uint32_t env_size; posix_spawn_file_actions_t actions; posix_spawnattr_t attr; sigset_t mask; long arg_max; uint32_t argspace_remaining; bool file_overflow = false; int result_log_level; char clockbuf[128]; const char *cwd = NULL; arg_max = sysconf(_SC_ARG_MAX); if (arg_max <= 0) { argspace_remaining = UINT_MAX; } else { argspace_remaining = (uint32_t)arg_max; } // Allow some misc working overhead argspace_remaining -= 32; stdin_fd = prepare_stdin(cmd, res); // Assumption: that only one thread will be executing on a given // cmd instance so that mutation of cmd->envht is safe. // This is guaranteed in the current architecture. if (cmd->max_files_stdin > 0 && res->num_results > cmd->max_files_stdin) { file_overflow = true; } // It is way too much of a hassle to try to recreate the clock value if it's // not a relative clock spec, and it's only going to happen on the first run // anyway, so just skip doing that entirely. if (since_spec && since_spec->tag == w_cs_clock && clock_id_string(since_spec->clock.root_number, since_spec->clock.ticks, clockbuf, sizeof(clockbuf))) { w_envp_set_cstring(cmd->envht, "WATCHMAN_SINCE", clockbuf); } else { w_envp_unset(cmd->envht, "WATCHMAN_SINCE"); } if (clock_id_string(res->root_number, res->ticks, clockbuf, sizeof(clockbuf))) { w_envp_set_cstring(cmd->envht, "WATCHMAN_CLOCK", clockbuf); } else { w_envp_unset(cmd->envht, "WATCHMAN_CLOCK"); } if (cmd->query->relative_root) { w_envp_set(cmd->envht, "WATCHMAN_RELATIVE_ROOT", cmd->query->relative_root); } else { w_envp_unset(cmd->envht, "WATCHMAN_RELATIVE_ROOT"); } // Compute args args = json_deep_copy(cmd->command); if (cmd->append_files) { // Measure how much space the base args take up for (i = 0; i < json_array_size(args); i++) { const char *ele = json_string_value(json_array_get(args, i)); argspace_remaining -= strlen(ele) + 1 + sizeof(char*); } // Dry run with env to compute space envp = w_envp_make_from_ht(cmd->envht, &env_size); free(envp); envp = NULL; argspace_remaining -= env_size; for (i = 0; i < res->num_results; i++) { // also: NUL terminator and entry in argv uint32_t size = res->results[i].relname->len + 1 + sizeof(char*); if (argspace_remaining < size) { file_overflow = true; break; } argspace_remaining -= size; json_array_append_new( args, json_string_nocheck(res->results[i].relname->buf) ); } } argv = w_argv_copy_from_json(args, 0); json_decref(args); args = NULL; w_envp_set_bool(cmd->envht, "WATCHMAN_FILES_OVERFLOW", file_overflow); envp = w_envp_make_from_ht(cmd->envht, &env_size); posix_spawnattr_init(&attr); sigemptyset(&mask); posix_spawnattr_setsigmask(&attr, &mask); posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK| #ifdef POSIX_SPAWN_CLOEXEC_DEFAULT // Darwin: close everything except what we put in file actions POSIX_SPAWN_CLOEXEC_DEFAULT| #endif POSIX_SPAWN_SETPGROUP); posix_spawn_file_actions_init(&actions); posix_spawn_file_actions_adddup2(&actions, stdin_fd, STDIN_FILENO); if (cmd->stdout_name) { posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, cmd->stdout_name, cmd->stdout_flags, 0666); } else { posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, STDOUT_FILENO); } if (cmd->stderr_name) { posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, cmd->stderr_name, cmd->stderr_flags, 0666); } else { posix_spawn_file_actions_adddup2(&actions, STDERR_FILENO, STDERR_FILENO); } pthread_mutex_lock(&spawn_lock); if (cmd->query->relative_root) { ignore_result(chdir(cmd->query->relative_root->buf)); } else { ignore_result(chdir(root->root_path->buf)); } json_unpack(cmd->definition, "{s:s}", "chdir", &cwd); if (cwd) { ignore_result(chdir(cwd)); } ret = posix_spawnp(&cmd->current_proc, argv[0], &actions, &attr, argv, envp); if (ret == 0) { w_root_addref(root); insert_running_pid(cmd->current_proc, root); } else { // On Darwin (at least), posix_spawn can fail but will still populate the // pid. Since we use the pid to gate future spawns, we need to ensure // that we clear out the pid on failure, otherwise the trigger would be // effectively disabled for the rest of the watch lifetime cmd->current_proc = 0; } ignore_result(chdir("/")); pthread_mutex_unlock(&spawn_lock); // If failed, we want to make sure we log enough info to figure out why result_log_level = res == 0 ? W_LOG_DBG : W_LOG_ERR; w_log(result_log_level, "posix_spawnp:\n"); for (i = 0; argv[i]; i++) { w_log(result_log_level, "argv[%d] %s\n", i, argv[i]); } for (i = 0; envp[i]; i++) { w_log(result_log_level, "envp[%d] %s\n", i, envp[i]); } w_log(result_log_level, "trigger %.*s:%s pid=%d ret=%d %s\n", (int)root->root_path->len, root->root_path->buf, cmd->triggername->buf, (int)cmd->current_proc, ret, strerror(ret)); free(argv); free(envp); posix_spawnattr_destroy(&attr); posix_spawn_file_actions_destroy(&actions); if (stdin_fd != -1) { close(stdin_fd); } }
/* 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 json_t *build_subscription_results( struct watchman_client_subscription *sub, w_root_t *root) { w_query_res res; json_t *response; json_t *file_list; char clockbuf[128]; struct w_clockspec *since_spec = sub->query->since_spec; if (since_spec && since_spec->tag == w_cs_clock) { w_log(W_LOG_DBG, "running subscription %s rules since %" PRIu32 "\n", sub->name->buf, since_spec->clock.ticks); } else { w_log(W_LOG_DBG, "running subscription %s rules (no since)\n", sub->name->buf); } // Subscriptions never need to sync explicitly; we are only dispatched // at settle points which are by definition sync'd to the present time sub->query->sync_timeout = 0; // We're called by the io thread, so there's little chance that the root // could be legitimately blocked by something else. That means that we // can use a short lock_timeout sub->query->lock_timeout = (uint32_t)cfg_get_int(root, "subscription_lock_timeout_ms", 100); if (!w_query_execute(sub->query, root, &res, subscription_generator, sub)) { w_log(W_LOG_ERR, "error running subscription %s query: %s", sub->name->buf, res.errmsg); w_query_result_free(&res); return NULL; } w_log(W_LOG_DBG, "subscription %s generated %" PRIu32 " results\n", sub->name->buf, res.num_results); if (res.num_results == 0) { update_subscription_ticks(sub, &res); w_query_result_free(&res); return NULL; } file_list = w_query_results_to_json(&sub->field_list, res.num_results, res.results); w_query_result_free(&res); response = make_response(); // It is way too much of a hassle to try to recreate the clock value if it's // not a relative clock spec, and it's only going to happen on the first run // anyway, so just skip doing that entirely. if (since_spec && since_spec->tag == w_cs_clock && clock_id_string(since_spec->clock.root_number, since_spec->clock.ticks, clockbuf, sizeof(clockbuf))) { set_unicode_prop(response, "since", clockbuf); } if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) { set_unicode_prop(response, "clock", clockbuf); } update_subscription_ticks(sub, &res); set_prop(response, "is_fresh_instance", json_boolean(res.is_fresh_instance)); set_prop(response, "files", file_list); set_prop(response, "root", w_string_to_json(root->root_path)); set_prop(response, "subscription", w_string_to_json(sub->name)); set_prop(response, "unilateral", json_true()); return response; }