/* parse an expression term. It can be one of: * "term" * ["term" <parameters>] */ std::unique_ptr<QueryExpr> w_query_expr_parse( w_query* query, const json_ref& exp) { w_string name; if (exp.isString()) { name = json_to_w_string(exp); } else if (exp.isArray() && json_array_size(exp) > 0) { const auto& first = exp.at(0); if (!first.isString()) { throw QueryParseError("first element of an expression must be a string"); } name = json_to_w_string(first); } else { throw QueryParseError("expected array or string for an expression"); } auto it = term_hash().find(name); if (it == term_hash().end()) { throw QueryParseError( watchman::to<std::string>("unknown expression term '", name, "'")); } return it->second(query, exp); }
static void cmd_debug_set_subscriptions_paused( struct watchman_client* clientbase, const json_ref& args) { auto client = (struct watchman_user_client*)clientbase; const auto& paused = args.at(1); auto& paused_map = paused.object(); for (auto& it : paused_map) { auto sub_iter = client->subscriptions.find(it.first); if (sub_iter == client->subscriptions.end()) { send_error_response( client, "this client does not have a subscription named '%s'", it.first.c_str()); return; } if (!json_is_boolean(it.second)) { send_error_response( client, "new value for subscription '%s' not a boolean", it.first.c_str()); return; } } auto states = json_object(); for (auto& it : paused_map) { auto sub_iter = client->subscriptions.find(it.first); bool old_paused = sub_iter->second->debug_paused; bool new_paused = json_is_true(it.second); sub_iter->second->debug_paused = new_paused; states.set( it.first, json_object({{"old", json_boolean(old_paused)}, {"new", it.second}})); } auto resp = make_response(); resp.set("paused", std::move(states)); send_and_dispose_response(clientbase, std::move(resp)); }
static bool query_caps( json_ref& response, json_ref& result, const json_ref& arr, bool required) { size_t i; bool have_all = true; for (i = 0; i < json_array_size(arr); i++) { const auto& ele = arr.at(i); const char* capname = json_string_value(ele); bool have = w_capability_supported(json_to_w_string(ele)); if (!have) { have_all = false; } if (!capname) { break; } result.set(capname, json_boolean(have)); if (required && !have) { char *buf = NULL; ignore_result(asprintf( &buf, "client required capability `%s` is not supported by this server", capname)); response.set("error", typed_string_to_json(buf, W_STRING_UNICODE)); w_log(W_LOG_ERR, "version: %s\n", buf); free(buf); // Only trigger the error on the first one we hit. Ideally // we'd tell the user about all of them, but it is a PITA to // join and print them here in C :-/ required = false; } } return have_all; }
/* version */ static void cmd_version(struct watchman_client* client, const json_ref& args) { auto resp = make_response(); #ifdef WATCHMAN_BUILD_INFO resp.set( "buildinfo", typed_string_to_json(WATCHMAN_BUILD_INFO, W_STRING_UNICODE)); #endif /* ["version"] * -> just returns the basic version information. * ["version", {"required": ["foo"], "optional": ["bar"]}] * -> includes capability matching information */ if (json_array_size(args) == 2) { const auto& arg_obj = args.at(1); auto req_cap = arg_obj.get_default("required"); auto opt_cap = arg_obj.get_default("optional"); auto cap_res = json_object_of_size( (opt_cap ? json_array_size(opt_cap) : 0) + (req_cap ? json_array_size(req_cap) : 0)); if (opt_cap && opt_cap.isArray()) { query_caps(resp, cap_res, opt_cap, false); } if (req_cap && req_cap.isArray()) { query_caps(resp, cap_res, req_cap, true); } resp.set("capabilities", std::move(cap_res)); } send_and_dispose_response(client, std::move(resp)); }
/* subscribe /root subname {query} * Subscribes the client connection to the specified root. */ static void cmd_subscribe( struct watchman_client* clientbase, const json_ref& args) { std::shared_ptr<watchman_client_subscription> sub; json_ref resp, initial_subscription_results; json_ref jfield_list; json_ref jname; std::shared_ptr<w_query> query; json_ref query_spec; int defer = true; /* can't use bool because json_unpack requires int */ json_ref defer_list; json_ref drop_list; struct watchman_user_client* client = (struct watchman_user_client*)clientbase; if (json_array_size(args) != 4) { send_error_response(client, "wrong number of arguments for subscribe"); return; } auto root = resolveRoot(client, args); jname = args.at(2); if (!json_is_string(jname)) { send_error_response( client, "expected 2nd parameter to be subscription name"); return; } query_spec = args.at(3); query = w_query_parse(root, query_spec); defer_list = query_spec.get_default("defer"); if (defer_list && !json_is_array(defer_list)) { send_error_response(client, "defer field must be an array of strings"); return; } drop_list = query_spec.get_default("drop"); if (drop_list && !json_is_array(drop_list)) { send_error_response(client, "drop field must be an array of strings"); return; } sub = std::make_shared<watchman_client_subscription>( root, client->shared_from_this()); sub->name = json_to_w_string(jname); sub->query = query; json_unpack(query_spec, "{s?:b}", "defer_vcs", &defer); sub->vcs_defer = defer; if (drop_list || defer_list) { size_t i; if (defer_list) { for (i = 0; i < json_array_size(defer_list); i++) { sub->drop_or_defer[json_to_w_string(json_array_get(defer_list, i))] = false; } } if (drop_list) { for (i = 0; i < json_array_size(drop_list); i++) { sub->drop_or_defer[json_to_w_string(json_array_get(drop_list, i))] = true; } } } // If they want SCM aware results we should wait for SCM events to finish // before dispatching subscriptions if (query->since_spec && query->since_spec->hasScmParams()) { sub->vcs_defer = true; // If they didn't specify any drop/defer behavior, default to a reasonable // setting that works together with the fsmonitor extension for hg. if (watchman::mapContainsAny( sub->drop_or_defer, "hg.update", "hg.transaction")) { sub->drop_or_defer["hg.update"] = false; // defer sub->drop_or_defer["hg.transaction"] = false; // defer } } // Connect the root to our subscription { auto client_id = watchman::to<std::string>(client); auto client_stream = watchman::to<std::string>(client->stm.get()); auto info_json = json_object( {{"name", w_string_to_json(sub->name)}, {"query", sub->query->query_spec}, {"client", w_string_to_json(w_string(client_id.data(), client_id.size()))}, {"stm", w_string_to_json( w_string(client_stream.data(), client_stream.size()))}, {"is_owner", json_boolean(client->stm->peerIsOwner())}, {"pid", json_integer(client->stm->getPeerProcessID())}}); std::weak_ptr<watchman_client> clientRef(client->shared_from_this()); client->unilateralSub.insert(std::make_pair( sub, root->unilateralResponses->subscribe( [clientRef, sub]() { auto client = clientRef.lock(); if (client) { client->ping->notify(); } }, info_json))); } client->subscriptions[sub->name] = sub; resp = make_response(); resp.set("subscribe", json_ref(jname)); add_root_warnings_to_response(resp, root); ClockSpec position; initial_subscription_results = sub->buildSubscriptionResults( root, position, OnStateTransition::DontAdvance); resp.set("clock", position.toJson()); send_and_dispose_response(client, std::move(resp)); if (initial_subscription_results) { send_and_dispose_response(client, std::move(initial_subscription_results)); } }
static void cmd_flush_subscriptions( struct watchman_client* clientbase, const json_ref& args) { auto client = (watchman_user_client*)clientbase; int sync_timeout; json_ref subs(nullptr); if (json_array_size(args) == 3) { auto& sync_timeout_obj = args.at(2).get("sync_timeout"); subs = args.at(2).get_default("subscriptions", nullptr); if (!json_is_integer(sync_timeout_obj)) { send_error_response(client, "'sync_timeout' must be an integer"); return; } sync_timeout = json_integer_value(sync_timeout_obj); } else { send_error_response( client, "wrong number of arguments to 'flush-subscriptions'"); return; } auto root = resolveRoot(client, args); std::vector<w_string> subs_to_sync; if (subs) { if (!json_is_array(subs)) { send_error_response( client, "expected 'subscriptions' to be an array of subscription names"); return; } for (auto& sub_name : subs.array()) { if (!json_is_string(sub_name)) { send_error_response( client, "expected 'subscriptions' to be an array of subscription names"); return; } auto& sub_name_str = json_to_w_string(sub_name); auto sub_iter = client->subscriptions.find(sub_name_str); if (sub_iter == client->subscriptions.end()) { send_error_response( client, "this client does not have a subscription named '%s'", sub_name_str.c_str()); return; } auto& sub = sub_iter->second; if (sub->root != root) { send_error_response( client, "subscription '%s' is on root '%s' different from command root " "'%s'", sub_name_str.c_str(), sub->root->root_path.c_str(), root->root_path.c_str()); return; } subs_to_sync.push_back(sub_name_str); } } else { // Look for all subscriptions matching this root. for (auto& sub_iter : client->subscriptions) { if (sub_iter.second->root == root) { subs_to_sync.push_back(sub_iter.first); } } } root->syncToNow(std::chrono::milliseconds(sync_timeout)); auto resp = make_response(); auto synced = json_array(); auto no_sync_needed = json_array(); auto dropped = json_array(); for (auto& sub_name_str : subs_to_sync) { auto sub_iter = client->subscriptions.find(sub_name_str); auto& sub = sub_iter->second; sub_action action; w_string policy_name; std::tie(action, policy_name) = get_subscription_action(sub.get(), root); if (action == sub_action::drop) { auto position = root->view()->getMostRecentRootNumberAndTickValue(); sub->last_sub_tick = position.ticks; sub->query->since_spec = watchman::make_unique<ClockSpec>(position); watchman::log( watchman::DBG, "(flush-subscriptions) dropping subscription notifications for ", sub->name, " until state ", policy_name, " is vacated. Advanced ticks to ", sub->last_sub_tick, "\n"); json_array_append(dropped, w_string_to_json(sub_name_str)); } else { // flush-subscriptions means that we _should NOT defer_ notifications. So // ignore defer and defer_vcs. ClockSpec out_position; watchman::log( watchman::DBG, "(flush-subscriptions) executing subscription ", sub->name, "\n"); auto sub_result = sub->buildSubscriptionResults( root, out_position, OnStateTransition::QueryAnyway); if (sub_result) { send_and_dispose_response(client, std::move(sub_result)); json_array_append(synced, w_string_to_json(sub_name_str)); } else { json_array_append(no_sync_needed, w_string_to_json(sub_name_str)); } } } resp.set({{"synced", std::move(synced)}, {"no_sync_needed", std::move(no_sync_needed)}, {"dropped", std::move(dropped)}}); add_root_warnings_to_response(resp, root); send_and_dispose_response(client, std::move(resp)); }
static std::unique_ptr<QueryExpr> parse( w_query* query, const json_ref& term) { std::unique_ptr<w_clockspec> spec; auto selected_field = since_what::SINCE_OCLOCK; const char* fieldname = "oclock"; if (!json_is_array(term)) { query->errmsg = strdup("\"since\" term must be an array"); return nullptr; } if (json_array_size(term) < 2 || json_array_size(term) > 3) { query->errmsg = strdup("\"since\" term has invalid number of parameters"); return nullptr; } const auto& jval = term.at(1); spec = w_clockspec_parse(jval); if (!spec) { query->errmsg = strdup("invalid clockspec for \"since\" term"); return nullptr; } if (spec->tag == w_cs_named_cursor) { query->errmsg = strdup("named cursors are not allowed in \"since\" terms"); return nullptr; } if (term.array().size() == 3) { const auto& field = term.at(2); size_t i; bool valid = false; fieldname = json_string_value(field); if (!fieldname) { query->errmsg = strdup("field name for \"since\" term must be a string"); return nullptr; } for (i = 0; i < sizeof(allowed_fields) / sizeof(allowed_fields[0]); ++i) { if (!strcmp(allowed_fields[i].label, fieldname)) { selected_field = allowed_fields[i].value; valid = true; break; } } if (!valid) { ignore_result(asprintf( &query->errmsg, "invalid field name \"%s\" for \"since\" term", fieldname)); return nullptr; } } switch (selected_field) { case since_what::SINCE_CTIME: case since_what::SINCE_MTIME: if (spec->tag != w_cs_timestamp) { ignore_result(asprintf( &query->errmsg, "field \"%s\" requires a timestamp value " "for comparison in \"since\" term", fieldname)); return nullptr; } break; case since_what::SINCE_OCLOCK: case since_what::SINCE_CCLOCK: /* we'll work with clocks or timestamps */ break; } return watchman::make_unique<SinceExpr>(std::move(spec), selected_field); }
static std::unique_ptr<QueryExpr> parse(w_query*, const json_ref& term, CaseSensitivity caseSensitive) { const char *pattern = nullptr, *scope = "basename"; const char *which = caseSensitive == CaseSensitivity::CaseInSensitive ? "iname" : "name"; std::unordered_set<w_string> set; if (!term.isArray()) { throw QueryParseError("Expected array for '", which, "' term"); } if (json_array_size(term) > 3) { throw QueryParseError( "Invalid number of arguments for '", which, "' term"); } if (json_array_size(term) == 3) { const auto& jscope = term.at(2); if (!jscope.isString()) { throw QueryParseError("Argument 3 to '", which, "' must be a string"); } scope = json_string_value(jscope); if (strcmp(scope, "basename") && strcmp(scope, "wholename")) { throw QueryParseError( "Invalid scope '", scope, "' for ", which, " expression"); } } const auto& name = term.at(1); if (name.isArray()) { uint32_t i; for (i = 0; i < json_array_size(name); i++) { if (!json_array_get(name, i).isString()) { throw QueryParseError( "Argument 2 to '", which, "' must be either a string or an array of string"); } } set.reserve(json_array_size(name)); for (i = 0; i < json_array_size(name); i++) { w_string element; const auto& jele = name.at(i); auto ele = json_to_w_string(jele); if (caseSensitive == CaseSensitivity::CaseInSensitive) { element = ele.piece().asLowerCase(ele.type()).normalizeSeparators(); } else { element = ele.normalizeSeparators(); } set.insert(element); } } else if (name.isString()) { pattern = json_string_value(name); } else { throw QueryParseError( "Argument 2 to '", which, "' must be either a string or an array of string"); } auto data = new NameExpr(std::move(set), caseSensitive, !strcmp(scope, "wholename")); if (pattern) { data->name = json_to_w_string(name).normalizeSeparators(); } return std::unique_ptr<QueryExpr>(data); }