/* 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); }
void watchman_root::applyIgnoreConfiguration() { uint8_t i; json_t *ignores; ignores = config.get("ignore_dirs"); if (!ignores) { return; } if (!json_is_array(ignores)) { w_log(W_LOG_ERR, "ignore_dirs must be an array of strings\n"); return; } for (i = 0; i < json_array_size(ignores); i++) { auto jignore = json_array_get(ignores, i); if (!json_is_string(jignore)) { w_log(W_LOG_ERR, "ignore_dirs must be an array of strings\n"); continue; } auto name = json_to_w_string(jignore); auto fullname = w_string::pathCat({root_path, name}); ignore.add(fullname, false); w_log(W_LOG_DBG, "ignoring %s recursively\n", fullname.c_str()); } }
/* unsubscribe /root subname * Cancels a subscription */ static void cmd_unsubscribe( struct watchman_client* clientbase, const json_ref& args) { const char *name; bool deleted{false}; struct watchman_user_client *client = (struct watchman_user_client *)clientbase; auto root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } auto jstr = json_array_get(args, 2); name = json_string_value(jstr); if (!name) { send_error_response( client, "expected 2nd parameter to be subscription name"); return; } auto sname = json_to_w_string(jstr); deleted = client->unsubByName(sname); auto resp = make_response(); resp.set({{"unsubscribe", typed_string_to_json(name)}, {"deleted", json_boolean(deleted)}}); send_and_dispose_response(client, std::move(resp)); }
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 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 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; }
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); }
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()); }
bool parse_field_list( json_ref field_list, w_query_field_list* selected, char** errmsg) { uint32_t i; selected->clear(); if (!field_list) { // Use the default list field_list = json_array({typed_string_to_json("name", W_STRING_UNICODE), typed_string_to_json("exists", W_STRING_UNICODE), typed_string_to_json("new", W_STRING_UNICODE), typed_string_to_json("size", W_STRING_UNICODE), typed_string_to_json("mode", W_STRING_UNICODE)}); } if (!json_is_array(field_list)) { *errmsg = strdup("field list must be an array of strings"); return false; } for (i = 0; i < json_array_size(field_list); i++) { auto jname = json_array_get(field_list, i); if (!json_is_string(jname)) { *errmsg = strdup("field list must be an array of strings"); return false; } auto name = json_to_w_string(jname); auto& defs = field_defs(); auto it = defs.find(name); if (it == defs.end()) { ignore_result(asprintf(errmsg, "unknown field name '%s'", name.c_str())); return false; } selected->push_back(&it->second); } 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"); } } }
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"); } }
/* 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 = resolve_root_or_err(client, args, 1, true); if (!root) { return; } 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 (sub->drop_or_defer.find("hg.update") == sub->drop_or_defer.end()) { sub->drop_or_defer["hg.update"] = false; // defer } } // Connect the root to our subscription { 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(); } }, sub->name))); } 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); 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 = resolve_root_or_err(client, args, 1, false); if (!root) { return; } 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); } } } if (!root->syncToNow(std::chrono::milliseconds(sync_timeout))) { send_error_response(client, "sync_timeout expired"); return; } 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); 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<watchman_stream> prepare_stdin( struct watchman_trigger_command* cmd, w_query_res* res) { char stdin_file_name[WATCHMAN_NAME_MAX]; if (cmd->stdin_style == trigger_input_style::input_dev_null) { return w_stm_open("/dev/null", O_RDONLY|O_CLOEXEC); } // Adjust result to fit within the specified limit if (cmd->max_files_stdin > 0) { auto& fileList = res->resultsArray.array(); auto n_files = std::min(size_t(cmd->max_files_stdin), fileList.size()); fileList.resize(std::min(fileList.size(), n_files)); } /* prepare the input stream for the child process */ snprintf( stdin_file_name, sizeof(stdin_file_name), "%s/wmanXXXXXX", watchman_tmp_dir); auto stdin_file = w_mkstemp(stdin_file_name); if (!stdin_file) { w_log(W_LOG_ERR, "unable to create a temporary file: %s %s\n", stdin_file_name, strerror(errno)); return NULL; } /* unlink the file, we don't need it in the filesystem; * we'll pass the fd on to the child as stdin */ unlink(stdin_file_name); // FIXME: windows path translation switch (cmd->stdin_style) { case input_json: { w_jbuffer_t buffer; w_log(W_LOG_ERR, "input_json: sending json object to stm\n"); if (!buffer.jsonEncodeToStream( res->resultsArray, stdin_file.get(), 0)) { w_log(W_LOG_ERR, "input_json: failed to write json data to stream: %s\n", strerror(errno)); return NULL; } break; } case input_name_list: for (auto& name : res->resultsArray.array()) { auto& nameStr = json_to_w_string(name); if (stdin_file->write(nameStr.data(), nameStr.size()) != (int)nameStr.size() || stdin_file->write("\n", 1) != 1) { w_log( W_LOG_ERR, "write failure while producing trigger stdin: %s\n", strerror(errno)); return nullptr; } } break; case input_dev_null: // already handled above break; } stdin_file->rewind(); return stdin_file; }