// must be called with the root locked // spec can be null, in which case a fresh instance is assumed void w_clockspec_eval(w_root_t *root, const struct w_clockspec *spec, struct w_query_since *since) { if (spec == NULL) { since->is_timestamp = false; since->clock.is_fresh_instance = true; since->clock.ticks = 0; return; } if (spec->tag == w_cs_timestamp) { // just copy the values over since->is_timestamp = true; since->timestamp = spec->timestamp; return; } since->is_timestamp = false; if (spec->tag == w_cs_named_cursor) { w_ht_val_t ticks_val; w_string_t *cursor = spec->named_cursor.cursor; since->clock.is_fresh_instance = !w_ht_lookup(root->cursors, w_ht_ptr_val(cursor), &ticks_val, false); if (!since->clock.is_fresh_instance) { since->clock.is_fresh_instance = ticks_val < root->last_age_out_tick; } if (since->clock.is_fresh_instance) { since->clock.ticks = 0; } else { since->clock.ticks = (uint32_t)ticks_val; } // 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_ptr_val(cursor), ++root->ticks); w_log(W_LOG_DBG, "resolved cursor %.*s -> %" PRIu32 "\n", cursor->len, cursor->buf, since->clock.ticks); return; } // spec->tag == w_cs_clock if (spec->clock.start_time == proc_start_time && spec->clock.pid == proc_pid && spec->clock.root_number == root->number) { since->clock.is_fresh_instance = spec->clock.ticks < root->last_age_out_tick; if (since->clock.is_fresh_instance) { since->clock.ticks = 0; } else { since->clock.ticks = spec->clock.ticks; } if (spec->clock.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 */ root->ticks++; } return; } // If the pid, start time or root number don't match, they asked a different // incarnation of the server or a different instance of this root, so we treat // them as having never spoken to us before since->clock.is_fresh_instance = true; since->clock.ticks = 0; }
/* 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); }
/* 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; 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; } 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); }
/* 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", w_string_to_json(cmd->triggername)); w_root_lock(root, "trigger-add"); 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_unicode_prop(resp, "disposition", "already_defined"); w_trigger_command_free(cmd); cmd = NULL; need_save = false; } else { set_unicode_prop(resp, "disposition", 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); }