/* 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); }
// Produces a string like: "`foo`, `bar`, and `baz`" std::string cfg_pretty_print_root_files(const json_ref& root_files) { std::string result; for (unsigned int i = 0; i < root_files.array().size(); ++i) { const auto& r = root_files.array()[i]; if (i > 1 && i == root_files.array().size() - 1) { // We are last in a list of multiple items result.append(", and "); } else if (i > 0) { result.append(", "); } result.append("`"); result.append(json_string_value(r)); result.append("`"); } return result; }
bool w_cmd_realpath_root(json_ref& args, char** errmsg) { const char *path; if (json_array_size(args) < 2) { ignore_result(asprintf(errmsg, "wrong number of arguments")); return false; } path = json_string_value(json_array_get(args, 1)); if (!path) { ignore_result(asprintf(errmsg, "second argument must be a string")); return false; } try { auto resolved = realPath(path); args.array()[1] = w_string_to_json(resolved); return true; } catch (const std::exception &exc) { watchman::log(watchman::DBG, "w_cmd_realpath_root: path ", path, " does not resolve: ", exc.what(), "\n"); // We don't treat this as an error; the caller will subsequently // fail and perform their usual error handling return true; } }
static void parse_suffixes(w_query* res, const json_ref& query) { size_t i; auto suffixes = query.get_default("suffix"); if (!suffixes) { return; } if (suffixes.isString()) { auto suff = parse_suffix(suffixes); res->suffixes.emplace_back(std::move(suff)); return; } if (!suffixes.isArray()) { throw QueryParseError("'suffix' must be a string or an array of strings"); } res->suffixes.reserve(json_array_size(suffixes)); for (i = 0; i < json_array_size(suffixes); i++) { const auto& ele = suffixes.at(i); if (!ele.isString()) { throw QueryParseError("'suffix' must be a string or an array of strings"); } auto suff = parse_suffix(ele); res->suffixes.emplace_back(std::move(suff)); } }
static w_string parse_suffix(const json_ref& ele) { if (!ele.isString()) { throw QueryParseError("'suffix' must be a string or an array of strings"); } auto str = json_to_w_string(ele); return str.piece().asLowerCase(str.type()); }
static void parse_query_expression(w_query* res, const json_ref& query) { auto exp = query.get_default("expression"); if (!exp) { // Empty expression means that we emit all generated files return; } res->expr = w_query_expr_parse(res, exp); }
static std::vector<w_string_piece> json_args_to_string_vec( const json_ref& args) { std::vector<w_string_piece> vec; for (auto& arg : args.array()) { vec.emplace_back(json_to_w_string(arg)); } return vec; }
static void parse_benchmark(w_query* res, const json_ref& query) { // Preserve behavior by supporting a boolean value. Also support int values. auto bench = query.get_default("bench"); if (bench) { if (bench.isBool()) { res->bench_iterations = 100; } else { res->bench_iterations = json_integer_value(bench); } } }
static void parse_request_id(w_query* res, const json_ref& query) { auto request_id = query.get_default("request_id"); if (!request_id) { return; } if (!request_id.isString()) { throw QueryParseError("'request_id' must be a string"); } res->request_id = json_to_w_string(request_id); }
// Return true if the json ref is an array of string values static bool is_array_of_strings(const json_ref& ref) { uint32_t i; if (!ref.isArray()) { return false; } for (i = 0; i < json_array_size(ref); i++) { if (!json_array_get(ref, i).isString()) { return false; } } return true; }
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; }
LocalSavedStateInterface::LocalSavedStateInterface( const json_ref& savedStateConfig, const SCM* scm) : SavedStateInterface(savedStateConfig), scm_(scm) { // Max commits to search in source control history for a saved state auto maxCommits = savedStateConfig.get_default("max-commits"); if (maxCommits) { if (!maxCommits.isInt()) { throw QueryParseError("'max-commits' must be an integer"); } maxCommits_ = json_integer_value(maxCommits); if (maxCommits_ < 1) { throw QueryParseError("'max-commits' must be a positive integer"); } } else { maxCommits_ = kDefaultMaxCommits; } // Local path to search for saved states. This path will only ever be read, // never written. auto localStoragePath = savedStateConfig.get_default("local-storage-path"); if (!localStoragePath) { throw QueryParseError( "'local-storage-path' must be present in saved state config"); } if (!localStoragePath.isString()) { throw QueryParseError("'local-storage-path' must be a string"); } localStoragePath_ = json_to_w_string(localStoragePath); if (!w_string_path_is_absolute(localStoragePath_)) { throw QueryParseError("'local-storage-path' must be an absolute path"); } // The saved state project must be a sub-directory in the local storage // path. if (w_string_path_is_absolute(project_)) { throw QueryParseError("'project' must be a relative path"); } }
static bool parse_since(w_query* res, const json_ref& query) { auto since = query.get_default("since"); if (!since) { return true; } auto spec = ClockSpec::parseOptionalClockSpec(since); if (spec) { // res owns the ref to spec res->since_spec = std::move(spec); return true; } throw QueryParseError("invalid value for 'since'"); }
static void parse_relative_root( const std::shared_ptr<w_root_t>& root, w_query* res, const json_ref& query) { auto relative_root = query.get_default("relative_root"); if (!relative_root) { return; } if (!relative_root.isString()) { throw QueryParseError("'relative_root' must be a string"); } auto path = json_to_w_string(relative_root).normalizeSeparators(); auto canon_path = w_string_canon_path(path); res->relative_root = w_string::pathCat({root->root_path, canon_path}); res->relative_root_slash = w_string::printf("%s/", res->relative_root.c_str()); }
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 parse_paths(w_query* res, const json_ref& query) { size_t i; auto paths = query.get_default("path"); if (!paths) { return true; } if (!paths.isArray()) { throw QueryParseError("'path' must be an array"); } res->paths.resize(json_array_size(paths)); for (i = 0; i < json_array_size(paths); i++) { const auto& ele = paths.at(i); w_string name; res->paths[i].depth = -1; if (ele.isString()) { name = json_to_w_string(ele); } else if (ele.isObject()) { name = json_to_w_string(ele.get("path")); auto depth = ele.get("depth"); if (!depth.isInt()) { throw QueryParseError("path.depth must be an integer"); } res->paths[i].depth = json_integer_value(depth); } else { throw QueryParseError( "expected object with 'path' and 'depth' properties"); } res->paths[i].name = name.normalizeSeparators(); } return true; }
void parse_globs(w_query* res, const json_ref& query) { size_t i; int noescape = 0; int includedotfiles = 0; auto globs = query.get_default("glob"); if (!globs) { return; } if (!json_is_array(globs)) { throw QueryParseError("'glob' must be an array"); } // Globs implicitly enable dedup_results mode res->dedup_results = true; if (json_unpack(query, "{s?b}", "glob_noescape", &noescape) != 0) { throw QueryParseError("glob_noescape must be a boolean"); } if (json_unpack(query, "{s?b}", "glob_includedotfiles", &includedotfiles) != 0) { throw QueryParseError("glob_includedotfiles must be a boolean"); } res->glob_flags = (includedotfiles ? 0 : WM_PERIOD) | (noescape ? WM_NOESCAPE : 0); res->glob_tree = watchman::make_unique<watchman_glob_tree>("", 0); for (i = 0; i < json_array_size(globs); i++) { const auto& ele = globs.at(i); const auto& pattern = json_to_w_string(ele); if (!add_glob(res->glob_tree.get(), pattern)) { throw QueryParseError("failed to compile multi-glob"); } } }
std::shared_ptr<w_query> w_query_parse( const std::shared_ptr<w_root_t>& root, const json_ref& query) { auto result = std::make_shared<w_query>(); auto res = result.get(); parse_benchmark(res, query); parse_case_sensitive(res, root, query); parse_sync(res, query); parse_dedup(res, query); parse_lock_timeout(res, query); parse_relative_root(root, res, query); parse_empty_on_fresh_instance(res, query); /* Look for path generators */ parse_paths(res, query); /* Look for glob generators */ parse_globs(res, query); /* Look for suffix generators */ parse_suffixes(res, query); /* Look for since generator */ parse_since(res, query); parse_query_expression(res, query); parse_request_id(res, query); parse_field_list(query.get_default("fields"), &res->fieldList); res->query_spec = query; return result; }
/* 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)); }
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); }
// Translate from the legacy array into the new style, then // delegate to the main parser. // We build a big anyof expression std::shared_ptr<w_query> w_query_parse_legacy( const std::shared_ptr<w_root_t>& root, const json_ref& args, int start, uint32_t* next_arg, const char* clockspec, json_ref* expr_p) { bool include = true; bool negated = false; uint32_t i; const char *term_name = "match"; json_ref included, excluded; auto query_obj = json_object(); if (!args.isArray()) { throw QueryParseError("Expected an array"); } for (i = start; i < json_array_size(args); i++) { const char *arg = json_string_value(json_array_get(args, i)); if (!arg) { /* not a string value! */ throw QueryParseError(watchman::to<std::string>( "rule @ position ", i, " is not a string value")); } } for (i = start; i < json_array_size(args); i++) { const char *arg = json_string_value(json_array_get(args, i)); if (!strcmp(arg, "--")) { i++; break; } if (!strcmp(arg, "-X")) { include = false; continue; } if (!strcmp(arg, "-I")) { include = true; continue; } if (!strcmp(arg, "!")) { negated = true; continue; } if (!strcmp(arg, "-P")) { term_name = "ipcre"; continue; } if (!strcmp(arg, "-p")) { term_name = "pcre"; continue; } // Which group are we going to file it into json_ref container; if (include) { if (!included) { included = json_array({typed_string_to_json("anyof", W_STRING_UNICODE)}); } container = included; } else { if (!excluded) { excluded = json_array({typed_string_to_json("anyof", W_STRING_UNICODE)}); } container = excluded; } auto term = json_array({typed_string_to_json(term_name, W_STRING_UNICODE), typed_string_to_json(arg), typed_string_to_json("wholename", W_STRING_UNICODE)}); if (negated) { term = json_array({typed_string_to_json("not", W_STRING_UNICODE), term}); } json_array_append_new(container, std::move(term)); // Reset negated flag negated = false; term_name = "match"; } if (excluded) { excluded = json_array({typed_string_to_json("not", W_STRING_UNICODE), excluded}); } json_ref query_array; if (included && excluded) { query_array = json_array( {typed_string_to_json("allof", W_STRING_UNICODE), excluded, included}); } else if (included) { query_array = included; } else { query_array = excluded; } // query_array may be NULL, which means find me all files. // Otherwise, it is the expression we want to use. if (query_array) { json_object_set_new_nocheck( query_obj, "expression", std::move(query_array)); } // For trigger if (next_arg) { *next_arg = i; } if (clockspec) { json_object_set_new_nocheck(query_obj, "since", typed_string_to_json(clockspec, W_STRING_UNICODE)); } /* compose the query with the field list */ auto query = w_query_parse(root, query_obj); if (expr_p) { *expr_p = query_obj; } if (query) { w_query_legacy_field_list(&query->fieldList); } return query; }
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 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)); }
/* 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)); } }