std::shared_ptr<w_root_t> root_resolve( const char* filename, bool auto_watch, bool* created, char** errmsg) { char *watch_path; int realpath_err; std::shared_ptr<w_root_t> root; *created = false; // Sanity check that the path is absolute if (!w_is_path_absolute_cstr(filename)) { ignore_result(asprintf(errmsg, "path \"%s\" must be absolute", filename)); w_log(W_LOG_ERR, "resolve_root: %s", *errmsg); return nullptr; } if (!strcmp(filename, "/")) { ignore_result(asprintf(errmsg, "cannot watch \"/\"")); w_log(W_LOG_ERR, "resolve_root: %s", *errmsg); return nullptr; } watch_path = w_realpath(filename); realpath_err = errno; if (!watch_path) { watch_path = (char*)filename; } w_string root_str(watch_path, W_STRING_BYTE); { auto map = watched_roots.rlock(); const auto& it = map->find(root_str); if (it != map->end()) { root = it->second; } } if (!root && watch_path == filename) { // Path didn't resolve and neither did the name they passed in ignore_result(asprintf(errmsg, "realpath(%s) -> %s", filename, strerror(realpath_err))); w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); return nullptr; } if (root || !auto_watch) { if (!root) { ignore_result( asprintf(errmsg, "directory %s is not watched", watch_path)); w_log(W_LOG_DBG, "resolve_root: %s\n", *errmsg); } if (watch_path != filename) { free(watch_path); } if (!root) { return nullptr; } // Treat this as new activity for aging purposes; this roughly maps // to a client querying something about the root and should extend // the lifetime of the root // Note that this write potentially races with the read in consider_reap // but we're "OK" with it because the latter is performed under a write // lock and the worst case side effect is that we (safely) decide to reap // at the same instant that a new command comes in. The reap intervals // are typically on the order of days. time(&root->inner.last_cmd_timestamp); return root; } w_log(W_LOG_DBG, "Want to watch %s -> %s\n", filename, watch_path); if (!check_allowed_fs(watch_path, errmsg)) { w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); if (watch_path != filename) { free(watch_path); } return nullptr; } if (!root_check_restrict(watch_path)) { ignore_result( asprintf(errmsg, "Your watchman administrator has configured watchman " "to prevent watching this path. None of the files " "listed in global config root_files are " "present and enforce_root_files is set to true")); w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); if (watch_path != filename) { free(watch_path); } return nullptr; } // created with 1 ref try { root = std::make_shared<w_root_t>(root_str); } catch (const std::exception& e) { watchman::log(watchman::ERR, "while making a new root: ", e.what()); *errmsg = strdup(e.what()); } if (watch_path != filename) { free(watch_path); } if (!root) { return nullptr; } { auto wlock = watched_roots.wlock(); auto& map = *wlock; auto& existing = map[root->root_path]; if (existing) { // Someone beat us in this race root = existing; *created = false; } else { existing = root; *created = true; } } return root; }
static void spawn_command( const std::shared_ptr<w_root_t>& root, struct watchman_trigger_command* cmd, w_query_res* res, struct w_clockspec* since_spec) { char **envp = NULL; uint32_t i = 0; int ret; char **argv = NULL; uint32_t env_size; posix_spawn_file_actions_t actions; posix_spawnattr_t attr; #ifndef _WIN32 sigset_t mask; #endif long arg_max; size_t argspace_remaining; bool file_overflow = false; int result_log_level; w_string_t *working_dir = NULL; #ifdef _WIN32 arg_max = 32*1024; #else arg_max = sysconf(_SC_ARG_MAX); #endif if (arg_max <= 0) { argspace_remaining = UINT_MAX; } else { argspace_remaining = (uint32_t)arg_max; } // Allow some misc working overhead argspace_remaining -= 32; // Record an overflow before we call prepare_stdin(), which mutates // and resizes the results to fit the specified limit. if (cmd->max_files_stdin > 0 && res->resultsArray.array().size() > cmd->max_files_stdin) { file_overflow = true; } auto stdin_file = prepare_stdin(cmd, res); if (!stdin_file) { w_log( W_LOG_ERR, "trigger %s:%s %s\n", root->root_path.c_str(), cmd->triggername.c_str(), strerror(errno)); return; } // Assumption: that only one thread will be executing on a given // cmd instance so that mutation of cmd->envht is safe. // This is guaranteed in the current architecture. // It is way too much of a hassle to try to recreate the clock value if it's // not a relative clock spec, and it's only going to happen on the first run // anyway, so just skip doing that entirely. if (since_spec && since_spec->tag == w_cs_clock) { w_envp_set_cstring( cmd->envht, "WATCHMAN_SINCE", since_spec->clock.position.toClockString().c_str()); } else { w_envp_unset(cmd->envht, "WATCHMAN_SINCE"); } w_envp_set_cstring( cmd->envht, "WATCHMAN_CLOCK", res->clockAtStartOfQuery.toClockString().c_str()); if (cmd->query->relative_root) { w_envp_set(cmd->envht, "WATCHMAN_RELATIVE_ROOT", cmd->query->relative_root); } else { w_envp_unset(cmd->envht, "WATCHMAN_RELATIVE_ROOT"); } // Compute args auto args = json_deep_copy(cmd->command); if (cmd->append_files) { // Measure how much space the base args take up for (i = 0; i < json_array_size(args); i++) { const char *ele = json_string_value(json_array_get(args, i)); argspace_remaining -= strlen(ele) + 1 + sizeof(char*); } // Dry run with env to compute space envp = w_envp_make_from_ht(cmd->envht, &env_size); free(envp); envp = NULL; argspace_remaining -= env_size; for (const auto& item : res->dedupedFileNames) { // also: NUL terminator and entry in argv uint32_t size = item.size() + 1 + sizeof(char*); if (argspace_remaining < size) { file_overflow = true; break; } argspace_remaining -= size; json_array_append_new(args, w_string_to_json(item)); } } argv = w_argv_copy_from_json(args, 0); args = nullptr; w_envp_set_bool(cmd->envht, "WATCHMAN_FILES_OVERFLOW", file_overflow); envp = w_envp_make_from_ht(cmd->envht, &env_size); posix_spawnattr_init(&attr); #ifndef _WIN32 sigemptyset(&mask); posix_spawnattr_setsigmask(&attr, &mask); #endif posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK| #ifdef POSIX_SPAWN_CLOEXEC_DEFAULT // Darwin: close everything except what we put in file actions POSIX_SPAWN_CLOEXEC_DEFAULT| #endif POSIX_SPAWN_SETPGROUP); posix_spawn_file_actions_init(&actions); #ifndef _WIN32 posix_spawn_file_actions_adddup2( &actions, stdin_file->getFileDescriptor(), STDIN_FILENO); #else posix_spawn_file_actions_adddup2_handle_np( &actions, stdin_file->getWindowsHandle(), STDIN_FILENO); #endif if (cmd->stdout_name) { posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, cmd->stdout_name, cmd->stdout_flags, 0666); } else { posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, STDOUT_FILENO); } if (cmd->stderr_name) { posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, cmd->stderr_name, cmd->stderr_flags, 0666); } else { posix_spawn_file_actions_adddup2(&actions, STDERR_FILENO, STDERR_FILENO); } // Figure out the appropriate cwd { const char *cwd = NULL; working_dir = NULL; if (cmd->query->relative_root) { working_dir = cmd->query->relative_root; } else { working_dir = root->root_path; } w_string_addref(working_dir); json_unpack(cmd->definition, "{s:s}", "chdir", &cwd); if (cwd) { w_string_t *cwd_str = w_string_new_typed(cwd, W_STRING_BYTE); if (w_is_path_absolute_cstr(cwd)) { w_string_delref(working_dir); working_dir = cwd_str; } else { w_string_t *joined; joined = w_string_path_cat(working_dir, cwd_str); w_string_delref(cwd_str); w_string_delref(working_dir); working_dir = joined; } } w_log(W_LOG_DBG, "using %.*s for working dir\n", working_dir->len, working_dir->buf); } #ifndef _WIN32 // This mutex is present to avoid fighting over the cwd when multiple // triggers run at the same time. It doesn't coordinate with all // possible chdir() calls, but this is the only place that we do this // in the watchman server process. static std::mutex cwdMutex; { std::unique_lock<std::mutex> lock(cwdMutex); ignore_result(chdir(working_dir->buf)); #else posix_spawnattr_setcwd_np(&attr, working_dir->buf); #endif w_string_delref(working_dir); working_dir = nullptr; ret = posix_spawnp(&cmd->current_proc, argv[0], &actions, &attr, argv, envp); if (ret != 0) { // On Darwin (at least), posix_spawn can fail but will still populate the // pid. Since we use the pid to gate future spawns, we need to ensure // that we clear out the pid on failure, otherwise the trigger would be // effectively disabled for the rest of the watch lifetime cmd->current_proc = 0; } #ifndef _WIN32 ignore_result(chdir("/")); } #endif // If failed, we want to make sure we log enough info to figure out why result_log_level = res == 0 ? W_LOG_DBG : W_LOG_ERR; w_log(result_log_level, "posix_spawnp: %s\n", cmd->triggername.c_str()); for (i = 0; argv[i]; i++) { w_log(result_log_level, "argv[%d] %s\n", i, argv[i]); } for (i = 0; envp[i]; i++) { w_log(result_log_level, "envp[%d] %s\n", i, envp[i]); } w_log( result_log_level, "trigger %s:%s pid=%d ret=%d %s\n", root->root_path.c_str(), cmd->triggername.c_str(), (int)cmd->current_proc, ret, strerror(ret)); free(argv); free(envp); posix_spawnattr_destroy(&attr); posix_spawn_file_actions_destroy(&actions); }