static json_ref make_type_field(const struct watchman_rule_match* match) { // Bias towards the more common file types first if (S_ISREG(match->file->stat.mode)) { return typed_string_to_json("f", W_STRING_UNICODE); } if (S_ISDIR(match->file->stat.mode)) { return typed_string_to_json("d", W_STRING_UNICODE); } if (S_ISLNK(match->file->stat.mode)) { return typed_string_to_json("l", W_STRING_UNICODE); } if (S_ISBLK(match->file->stat.mode)) { return typed_string_to_json("b", W_STRING_UNICODE); } if (S_ISCHR(match->file->stat.mode)) { return typed_string_to_json("c", W_STRING_UNICODE); } if (S_ISFIFO(match->file->stat.mode)) { return typed_string_to_json("p", W_STRING_UNICODE); } if (S_ISSOCK(match->file->stat.mode)) { return typed_string_to_json("s", W_STRING_UNICODE); } #ifdef S_ISDOOR if (S_ISDOOR(match->file->stat.mode)) { return typed_string_to_json("D", W_STRING_UNICODE); } #endif return typed_string_to_json("?", W_STRING_UNICODE); }
/* get-sockname */ static void cmd_get_sockname(struct watchman_client* client, const json_ref&) { auto resp = make_response(); resp.set("sockname", typed_string_to_json(get_sock_name(), W_STRING_BYTE)); send_and_dispose_response(client, std::move(resp)); }
void send_error_response(struct watchman_client *client, const char *fmt, ...) { char buf[WATCHMAN_NAME_MAX]; va_list ap; json_t *resp = make_response(); json_t *errstr; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); errstr = typed_string_to_json(buf, W_STRING_MIXED); set_prop(resp, "error", errstr); json_incref(errstr); w_perf_add_meta(&client->perf_sample, "error", errstr); if (client->current_command) { char *command = NULL; command = json_dumps(client->current_command, 0); w_log(W_LOG_ERR, "send_error_response: %s failed: %s\n", command, buf); free(command); } else { w_log(W_LOG_ERR, "send_error_response: %s\n", buf); } send_and_dispose_response(client, resp); }
/* 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)); }
// Compute the effective value of the root_files configuration and // return a json reference. The caller must decref the ref when done // (we may synthesize this value). Sets enforcing to indicate whether // we will only allow watches on the root_files. // The array returned by this function (if not NULL) is guaranteed to // list .watchmanconfig as its zeroth element. json_ref cfg_compute_root_files(bool* enforcing) { *enforcing = false; json_ref ref = cfg_get_json("enforce_root_files"); if (ref) { if (!ref.isBool()) { w_log(W_LOG_FATAL, "Expected config value enforce_root_files to be boolean\n"); } *enforcing = ref.asBool(); } ref = cfg_get_json("root_files"); if (ref) { if (!is_array_of_strings(ref)) { w_log(W_LOG_FATAL, "global config root_files must be an array of strings\n"); *enforcing = false; return nullptr; } prepend_watchmanconfig_to_array(ref); return ref; } // Try legacy root_restrict_files configuration ref = cfg_get_json("root_restrict_files"); if (ref) { if (!is_array_of_strings(ref)) { w_log(W_LOG_FATAL, "deprecated global config root_restrict_files " "must be an array of strings\n"); *enforcing = false; return nullptr; } prepend_watchmanconfig_to_array(ref); *enforcing = true; return ref; } // Synthesize our conservative default value. // .watchmanconfig MUST be first return json_array({typed_string_to_json(".watchmanconfig"), typed_string_to_json(".hg"), typed_string_to_json(".git"), typed_string_to_json(".svn")}); }
// Given an array of string values, if that array does not contain // a ".watchmanconfig" entry, prepend it static void prepend_watchmanconfig_to_array(json_ref& ref) { const char *val; if (json_array_size(ref) == 0) { // json_array_insert_new at index can fail when the array is empty, // so just append in this case. json_array_append_new(ref, typed_string_to_json(".watchmanconfig", W_STRING_UNICODE)); return; } val = json_string_value(json_array_get(ref, 0)); if (!strcmp(val, ".watchmanconfig")) { return; } json_array_insert_new(ref, 0, typed_string_to_json(".watchmanconfig", W_STRING_UNICODE)); }
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; }
static json_ref parse_value(lex_t *lex, size_t flags, json_error_t *error) { json_ref json; switch(lex->token) { case TOKEN_STRING: { json = typed_string_to_json(lex->value.string, W_STRING_BYTE); break; } case TOKEN_INTEGER: { json = json_integer(lex->value.integer); break; } case TOKEN_REAL: { json = json_real(lex->value.real); break; } case TOKEN_TRUE: json = json_true(); break; case TOKEN_FALSE: json = json_false(); break; case TOKEN_NULL: json = json_null(); break; case '{': json = parse_object(lex, flags, error); break; case '[': json = parse_array(lex, flags, error); break; case TOKEN_INVALID: error_set(error, lex, "invalid token"); return nullptr; default: error_set(error, lex, "unexpected token"); return nullptr; } if(!json) return nullptr; return json; }
void w_query_legacy_field_list(w_query_field_list* flist) { static const char *names[] = { "name", "exists", "size", "mode", "uid", "gid", "mtime", "ctime", "ino", "dev", "nlink", "new", "cclock", "oclock" }; uint8_t i; auto list = json_array(); for (i = 0; i < sizeof(names)/sizeof(names[0]); i++) { json_array_append_new(list, typed_string_to_json(names[i], W_STRING_UNICODE)); } parse_field_list(list, flist); }
void add_root_warnings_to_response(json_t *response, w_root_t *root) { char *str = NULL; char *full = NULL; if (!root->last_recrawl_reason && !root->warning) { return; } if (cfg_get_bool(root, "suppress_recrawl_warnings", false)) { return; } if (root->last_recrawl_reason) { ignore_result( asprintf(&str, "Recrawled this watch %d times, most recently because:\n" "%.*s\n" "To resolve, please review the information on\n" "%s#recrawl", root->recrawl_count, root->last_recrawl_reason->len, root->last_recrawl_reason->buf, cfg_get_trouble_url())); } ignore_result(asprintf( &full, "%.*s%s" // root->warning "%s\n" // str (last recrawl reason) "To clear this warning, run:\n" "`watchman watch-del %.*s ; watchman watch-project %.*s`\n", root->warning ? root->warning->len : 0, root->warning ? root->warning->buf : "", root->warning && str ? "\n" : "", // newline if we have both strings str ? str : "", root->root_path->len, root->root_path->buf, root->root_path->len, root->root_path->buf)); if (full) { set_prop(response, "warning", typed_string_to_json(full, W_STRING_MIXED)); } free(str); free(full); }
static void cmd_debug_poison( struct watchman_client* client, const json_ref& args) { struct timeval now; auto root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } gettimeofday(&now, NULL); set_poison_state( root->root_path, now, "debug-poison", std::error_code(ENOMEM, std::generic_category())); auto resp = make_response(); resp.set("poison", typed_string_to_json(poisoned_reason, W_STRING_UNICODE)); send_and_dispose_response(client, 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)); }
// 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; }
void PerfLogThread::loop() { json_ref samples; char **envp; json_ref perf_cmd; int64_t sample_batch; w_set_thread_name("perflog"); // Prep some things that we'll need each time we run a command { uint32_t env_size; auto envpht = w_envp_make_ht(); char *statedir = dirname(strdup(watchman_state_file)); w_envp_set_cstring(envpht, "WATCHMAN_STATE_DIR", statedir); w_envp_set_cstring(envpht, "WATCHMAN_SOCK", get_sock_name()); envp = w_envp_make_from_ht(envpht, &env_size); } perf_cmd = cfg_get_json("perf_logger_command"); if (json_is_string(perf_cmd)) { perf_cmd = json_array({perf_cmd}); } if (!json_is_array(perf_cmd)) { w_log( W_LOG_FATAL, "perf_logger_command must be either a string or an array of strings\n"); } sample_batch = cfg_get_int("perf_logger_command_max_samples_per_call", 4); while (!w_is_stopping()) { { auto wlock = samples_.wlock(); if (!*wlock) { cond_.wait(wlock.getUniqueLock()); } samples = nullptr; std::swap(samples, *wlock); } if (samples) { while (json_array_size(samples) > 0) { int i = 0; auto cmd = json_array(); posix_spawnattr_t attr; posix_spawn_file_actions_t actions; pid_t pid; char **argv = NULL; json_array_extend(cmd, perf_cmd); while (i < sample_batch && json_array_size(samples) > 0) { char *stringy = json_dumps(json_array_get(samples, 0), 0); if (stringy) { json_array_append_new( cmd, typed_string_to_json(stringy, W_STRING_MIXED)); free(stringy); } json_array_remove(samples, 0); i++; } argv = w_argv_copy_from_json(cmd, 0); if (!argv) { char *dumped = json_dumps(cmd, 0); w_log(W_LOG_FATAL, "error converting %s to an argv array\n", dumped); } posix_spawnattr_init(&attr); #ifdef POSIX_SPAWN_CLOEXEC_DEFAULT posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT); #endif posix_spawn_file_actions_init(&actions); posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, "/dev/null", O_RDONLY, 0666); posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0666); posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0666); if (posix_spawnp(&pid, argv[0], &actions, &attr, argv, envp) == 0) { int status; while (waitpid(pid, &status, 0) != pid) { if (errno != EINTR) { break; } } } else { int err = errno; w_log(W_LOG_ERR, "failed to spawn %s: %s\n", argv[0], strerror(err)); } posix_spawnattr_destroy(&attr); posix_spawn_file_actions_destroy(&actions); free(argv); } } } }
void watchman_perf_sample::log() { char *dumped = NULL; if (!will_log) { return; } // Assemble a perf blob auto info = json_object( {{"description", typed_string_to_json(description)}, {"meta", meta_data}, {"pid", json_integer(getpid())}, {"version", typed_string_to_json(PACKAGE_VERSION, W_STRING_UNICODE)}}); #ifdef WATCHMAN_BUILD_INFO info.set( "buildinfo", typed_string_to_json(WATCHMAN_BUILD_INFO, W_STRING_UNICODE)); #endif #define ADDTV(name, tv) info.set(name, json_real(w_timeval_abs_seconds(tv))) ADDTV("elapsed_time", duration); ADDTV("start_time", time_begin); #ifdef HAVE_SYS_RESOURCE_H ADDTV("user_time", usage.ru_utime); ADDTV("system_time", usage.ru_stime); #define ADDU(n) info.set(#n, json_integer(usage.n)) ADDU(ru_maxrss); ADDU(ru_ixrss); ADDU(ru_idrss); ADDU(ru_minflt); ADDU(ru_majflt); ADDU(ru_nswap); ADDU(ru_inblock); ADDU(ru_oublock); ADDU(ru_msgsnd); ADDU(ru_msgrcv); ADDU(ru_nsignals); ADDU(ru_nvcsw); ADDU(ru_nivcsw); #endif // HAVE_SYS_RESOURCE_H #undef ADDU #undef ADDTV // Log to the log file dumped = json_dumps(info, 0); w_log(W_LOG_ERR, "PERF: %s\n", dumped); free(dumped); if (!cfg_get_json("perf_logger_command")) { return; } // Send this to our logging thread for async processing { // The common case is that we already set up the logging // thread and that we can just log through it. auto rlock = perfThread.rlock(); if (rlock->get()) { (*rlock)->addSample(std::move(info)); return; } } // If it wasn't set, then we need an exclusive lock to // make sure that we don't spawn multiple instances. { auto wlock = perfThread.wlock(); if (!wlock->get()) { *wlock = watchman::make_unique<PerfLogThread>(); } (*wlock)->addSample(std::move(info)); } }
static void *perf_log_thread(void *unused) { json_t *samples = NULL; char **envp; json_t *perf_cmd; int64_t sample_batch; unused_parameter(unused); w_set_thread_name("perflog"); // Prep some things that we'll need each time we run a command { uint32_t env_size; w_ht_t *envpht = w_envp_make_ht(); char *statedir = dirname(strdup(watchman_state_file)); w_envp_set_cstring(envpht, "WATCHMAN_STATE_DIR", statedir); w_envp_set_cstring(envpht, "WATCHMAN_SOCK", get_sock_name()); envp = w_envp_make_from_ht(envpht, &env_size); } perf_cmd = cfg_get_json(NULL, "perf_logger_command"); if (json_is_string(perf_cmd)) { perf_cmd = json_pack("[O]", perf_cmd); } if (!json_is_array(perf_cmd)) { w_log( W_LOG_FATAL, "perf_logger_command must be either a string or an array of strings\n"); } sample_batch = cfg_get_int(NULL, "perf_logger_command_max_samples_per_call", 4); while (true) { pthread_mutex_lock(&perf_log_lock); if (!perf_log_samples) { pthread_cond_wait(&perf_log_cond, &perf_log_lock); } samples = perf_log_samples; perf_log_samples = NULL; pthread_mutex_unlock(&perf_log_lock); if (samples) { while (json_array_size(samples) > 0) { int i = 0; json_t *cmd = json_array(); posix_spawnattr_t attr; posix_spawn_file_actions_t actions; pid_t pid; char **argv = NULL; json_array_extend(cmd, perf_cmd); while (i < sample_batch && json_array_size(samples) > 0) { char *stringy = json_dumps(json_array_get(samples, 0), 0); json_array_append(cmd, typed_string_to_json(stringy, W_STRING_MIXED)); free(stringy); json_array_remove(samples, 0); i++; } argv = w_argv_copy_from_json(cmd, 0); if (!argv) { char *dumped = json_dumps(cmd, 0); w_log(W_LOG_FATAL, "error converting %s to an argv array\n", dumped); } posix_spawnattr_init(&attr); #ifdef POSIX_SPAWN_CLOEXEC_DEFAULT posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT); #endif posix_spawn_file_actions_init(&actions); posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, "/dev/null", O_RDONLY, 0666); posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0666); posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0666); if (posix_spawnp(&pid, argv[0], &actions, &attr, argv, envp) == 0) { // There's no sense waiting here, because w_reap_children is called // by the reaper thread. } else { int err = errno; w_log(W_LOG_ERR, "failed to spawn %s: %s\n", argv[0], strerror(err)); } posix_spawnattr_destroy(&attr); posix_spawn_file_actions_destroy(&actions); free(argv); json_decref(cmd); } json_decref(samples); } } return NULL; }