static bool check_allowed_fs(const char *filename, char **errmsg) { auto fs_type = w_fstype(filename); json_t *illegal_fstypes = NULL; json_t *advice_string; uint32_t i; const char *advice = NULL; // Report this to the log always, as it is helpful in understanding // problem reports w_log( W_LOG_ERR, "path %s is on filesystem type %s\n", filename, fs_type.c_str()); illegal_fstypes = cfg_get_json("illegal_fstypes"); if (!illegal_fstypes) { return true; } advice_string = cfg_get_json("illegal_fstypes_advice"); if (advice_string) { advice = json_string_value(advice_string); } if (!advice) { advice = "relocate the dir to an allowed filesystem type"; } if (!json_is_array(illegal_fstypes)) { w_log(W_LOG_ERR, "resolve_root: global config illegal_fstypes is not an array\n"); return true; } for (i = 0; i < json_array_size(illegal_fstypes); i++) { json_t *obj = json_array_get(illegal_fstypes, i); const char *name = json_string_value(obj); if (!name) { w_log(W_LOG_ERR, "resolve_root: global config illegal_fstypes " "element %" PRIu32 " should be a string\n", i); continue; } if (!w_string_equal_cstring(fs_type, name)) { continue; } ignore_result(asprintf( errmsg, "path uses the \"%s\" filesystem " "and is disallowed by global config illegal_fstypes: %s", fs_type.c_str(), advice)); return false; } return true; }
// 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_t *cfg_compute_root_files(bool *enforcing) { json_t *ref; // This is completely undocumented and will go away soon. Do not document or // use! bool ignore_watchmanconfig = cfg_get_bool(NULL, "_ignore_watchmanconfig", false); *enforcing = false; ref = cfg_get_json(NULL, "enforce_root_files"); if (ref) { if (!json_is_boolean(ref)) { w_log(W_LOG_FATAL, "Expected config value enforce_root_files to be boolean\n"); } *enforcing = json_is_true(ref); } ref = cfg_get_json(NULL, "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 NULL; } prepend_watchmanconfig_to_array(ref); json_incref(ref); return ref; } // Try legacy root_restrict_files configuration ref = cfg_get_json(NULL, "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 NULL; } if (!ignore_watchmanconfig) { prepend_watchmanconfig_to_array(ref); } json_incref(ref); *enforcing = true; return ref; } // Synthesize our conservative default value. // .watchmanconfig MUST be first if (!ignore_watchmanconfig) { return json_pack("[ssss]", ".watchmanconfig", ".hg", ".git", ".svn"); } else { return json_pack("[sss]", ".hg", ".git", ".svn"); } }
// 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_t *cfg_compute_root_files(bool *enforcing) { json_t *ref; *enforcing = false; ref = cfg_get_json(NULL, "enforce_root_files"); if (ref) { if (!json_is_boolean(ref)) { w_log(W_LOG_FATAL, "Expected config value enforce_root_files to be boolean\n"); } *enforcing = json_is_true(ref); } ref = cfg_get_json(NULL, "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 NULL; } prepend_watchmanconfig_to_array(ref); json_incref(ref); return ref; } // Try legacy root_restrict_files configuration ref = cfg_get_json(NULL, "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 NULL; } prepend_watchmanconfig_to_array(ref); json_incref(ref); *enforcing = true; return ref; } // Synthesize our conservative default value. // .watchmanconfig MUST be first return json_pack("[ssss]", ".watchmanconfig", ".hg", ".git", ".svn"); }
// 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")}); }
double cfg_get_double(w_root_t *root, const char *name, double defval) { json_t *val = cfg_get_json(root, name); if (val) { if (!json_is_number(val)) { w_log(W_LOG_FATAL, "Expected config value %s to be a number\n", name); } return json_real_value(val); } return defval; }
bool cfg_get_bool(w_root_t *root, const char *name, bool defval) { json_t *val = cfg_get_json(root, name); if (val) { if (!json_is_boolean(val)) { w_log(W_LOG_FATAL, "Expected config value %s to be a boolean\n", name); } return json_is_true(val); } return defval; }
const char* cfg_get_string(const char* name, const char* defval) { auto val = cfg_get_json(name); if (val) { if (!val.isString()) { throw std::runtime_error(watchman::to<std::string>( "Expected config value ", name, " to be a string")); } return json_string_value(val); } return defval; }
double cfg_get_double(const char* name, double defval) { auto val = cfg_get_json(name); if (val) { if (!val.isNumber()) { throw std::runtime_error(watchman::to<std::string>( "Expected config value ", name, " to be a number")); } return json_real_value(val); } return defval; }
json_int_t cfg_get_int(const char* name, json_int_t defval) { auto val = cfg_get_json(name); if (val) { if (!val.isInt()) { throw std::runtime_error(watchman::to<std::string>( "Expected config value ", name, " to be an integer")); } return json_integer_value(val); } return defval; }
bool cfg_get_bool(const char* name, bool defval) { auto val = cfg_get_json(name); if (val) { if (!val.isBool()) { throw std::runtime_error(watchman::to<std::string>( "Expected config value ", name, " to be a boolean")); } return val.asBool(); } return defval; }
bool w_perf_finish(w_perf_t *perf) { gettimeofday(&perf->time_end, NULL); w_timeval_sub(perf->time_end, perf->time_begin, &perf->duration); #ifdef HAVE_SYS_RESOURCE_H getrusage(RUSAGE_SELF, &perf->usage_end); // Compute the delta for the usage w_timeval_sub(perf->usage_end.ru_utime, perf->usage_begin.ru_utime, &perf->usage.ru_utime); w_timeval_sub(perf->usage_end.ru_stime, perf->usage_begin.ru_stime, &perf->usage.ru_stime); #define DIFFU(n) perf->usage.n = perf->usage_end.n - perf->usage_begin.n DIFFU(ru_maxrss); DIFFU(ru_ixrss); DIFFU(ru_idrss); DIFFU(ru_minflt); DIFFU(ru_majflt); DIFFU(ru_nswap); DIFFU(ru_inblock); DIFFU(ru_oublock); DIFFU(ru_msgsnd); DIFFU(ru_msgrcv); DIFFU(ru_nsignals); DIFFU(ru_nvcsw); DIFFU(ru_nivcsw); #undef DIFFU #endif if (!perf->will_log) { if (perf->wall_time_elapsed_thresh == 0) { json_t *thresh = cfg_get_json(NULL, "perf_sampling_thresh"); if (thresh) { if (json_is_number(thresh)) { perf->wall_time_elapsed_thresh = json_number_value(thresh); } else { json_unpack(thresh, "{s:f}", perf->description, &perf->wall_time_elapsed_thresh); } } } if (perf->wall_time_elapsed_thresh > 0 && w_timeval_diff(perf->time_begin, perf->time_end) > perf->wall_time_elapsed_thresh) { perf->will_log = true; } } return perf->will_log; }
json_int_t cfg_get_int(w_root_t *root, const char *name, json_int_t defval) { json_t *val = cfg_get_json(root, name); if (val) { if (!json_is_integer(val)) { w_log(W_LOG_FATAL, "Expected config value %s to be an integer\n", name); } return json_integer_value(val); } return defval; }
const char *cfg_get_string(w_root_t *root, const char *name, const char *defval) { json_t *val = cfg_get_json(root, name); if (val) { if (!json_is_string(val)) { w_log(W_LOG_FATAL, "Expected config value %s to be a string\n", name); } return json_string_value(val); } return defval; }
bool watchman_perf_sample::finish() { gettimeofday(&time_end, nullptr); w_timeval_sub(time_end, time_begin, &duration); #ifdef HAVE_SYS_RESOURCE_H getrusage(RUSAGE_SELF, &usage_end); // Compute the delta for the usage w_timeval_sub(usage_end.ru_utime, usage_begin.ru_utime, &usage.ru_utime); w_timeval_sub(usage_end.ru_stime, usage_begin.ru_stime, &usage.ru_stime); #define DIFFU(n) usage.n = usage_end.n - usage_begin.n DIFFU(ru_maxrss); DIFFU(ru_ixrss); DIFFU(ru_idrss); DIFFU(ru_minflt); DIFFU(ru_majflt); DIFFU(ru_nswap); DIFFU(ru_inblock); DIFFU(ru_oublock); DIFFU(ru_msgsnd); DIFFU(ru_msgrcv); DIFFU(ru_nsignals); DIFFU(ru_nvcsw); DIFFU(ru_nivcsw); #undef DIFFU #endif if (!will_log) { if (wall_time_elapsed_thresh == 0) { auto thresh = cfg_get_json("perf_sampling_thresh"); if (thresh) { if (json_is_number(thresh)) { wall_time_elapsed_thresh = json_number_value(thresh); } else { json_unpack(thresh, "{s:f}", description, &wall_time_elapsed_thresh); } } } if (wall_time_elapsed_thresh > 0 && w_timeval_diff(time_begin, time_end) > wall_time_elapsed_thresh) { will_log = true; } } return will_log; }
/** * This function expects the config to be an object containing the keys 'group' * and 'others', each a bool. */ mode_t cfg_get_perms(const char* name, bool write_bits, bool execute_bits) { auto val = cfg_get_json(name); mode_t ret = S_IRUSR | S_IWUSR; if (execute_bits) { ret |= S_IXUSR; } if (val) { if (!val.isObject()) { w_log(W_LOG_FATAL, "Expected config value %s to be an object\n", name); } ret |= get_group_perm(name, val, write_bits, execute_bits); ret |= get_others_perm(name, val, write_bits, execute_bits); } return ret; }
void w_perf_log(w_perf_t *perf) { json_t *info; char *dumped = NULL; if (!perf->will_log) { return; } // Assemble a perf blob info = json_pack("{s:u, s:O, s:i, s:u}", // "description", perf->description, // "meta", perf->meta_data, // "pid", getpid(), // "version", PACKAGE_VERSION // ); #ifdef WATCHMAN_BUILD_INFO set_unicode_prop(info, "buildinfo", WATCHMAN_BUILD_INFO); #endif #define ADDTV(name, tv) \ set_prop(info, name, json_real(w_timeval_abs_seconds(tv))) ADDTV("elapsed_time", perf->duration); ADDTV("start_time", perf->time_begin); #ifdef HAVE_SYS_RESOURCE_H ADDTV("user_time", perf->usage.ru_utime); ADDTV("system_time", perf->usage.ru_stime); #define ADDU(n) set_prop(info, #n, json_integer(perf->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(NULL, "perf_logger_command")) { json_decref(info); return; } // Send this to our logging thread for async processing pthread_mutex_lock(&perf_log_lock); if (!perf_log_thread_started) { pthread_cond_init(&perf_log_cond, NULL); pthread_create(&perf_log_thr, NULL, perf_log_thread, NULL); perf_log_thread_started = true; } if (!perf_log_samples) { perf_log_samples = json_array(); } json_array_append_new(perf_log_samples, info); pthread_mutex_unlock(&perf_log_lock); pthread_cond_signal(&perf_log_cond); }
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; }
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)); } }
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); } } } }