in watchman/TriggerCommand.cpp [137:286]
void spawn_command(
const std::shared_ptr<Root>& root,
struct TriggerCommand* cmd,
QueryResult* res,
ClockSpec* since_spec) {
bool file_overflow = false;
size_t arg_max = ChildProcess::getArgMax();
size_t argspace_remaining = 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) {
logf(
ERR,
"trigger {}:{} {}\n",
root->root_path,
cmd->triggername,
folly::errnoStr(errno));
return;
}
// Assumption: that only one thread will be executing on a given
// cmd instance so that mutation of cmd->env 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 (const auto* clock = since_spec
? std::get_if<ClockSpec::Clock>(&since_spec->spec)
: nullptr) {
cmd->env.set("WATCHMAN_SINCE", clock->position.toClockString());
} else {
cmd->env.unset("WATCHMAN_SINCE");
}
cmd->env.set(
"WATCHMAN_CLOCK", res->clockAtStartOfQuery.position().toClockString());
if (cmd->query->relative_root) {
cmd->env.set("WATCHMAN_RELATIVE_ROOT", cmd->query->relative_root);
} else {
cmd->env.unset("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 (size_t 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
size_t env_size;
cmd->env.asEnviron(&env_size);
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));
}
}
cmd->env.setBool("WATCHMAN_FILES_OVERFLOW", file_overflow);
ChildProcess::Options opts;
opts.environment() = cmd->env;
#ifndef _WIN32
sigset_t mask;
sigemptyset(&mask);
opts.setSigMask(mask);
#endif
opts.setFlags(POSIX_SPAWN_SETPGROUP);
opts.dup2(stdin_file->getFileDescriptor(), STDIN_FILENO);
if (!cmd->stdout_name.empty()) {
opts.open(STDOUT_FILENO, cmd->stdout_name.c_str(), cmd->stdout_flags, 0666);
} else {
opts.dup2(FileDescriptor::stdOut(), STDOUT_FILENO);
}
if (!cmd->stderr_name.empty()) {
opts.open(STDERR_FILENO, cmd->stderr_name.c_str(), cmd->stderr_flags, 0666);
} else {
opts.dup2(FileDescriptor::stdErr(), STDERR_FILENO);
}
// Figure out the appropriate cwd
w_string working_dir(cmd->query->relative_root);
if (!working_dir) {
working_dir = root->root_path;
}
auto cwd = cmd->definition.get_default("chdir");
if (cwd) {
auto target = json_to_w_string(cwd);
if (w_is_path_absolute_cstr_len(target.data(), target.size())) {
working_dir = target;
} else {
working_dir = w_string::pathCat({working_dir, target});
}
}
log(DBG, "using ", working_dir, " for working dir\n");
opts.chdir(working_dir.c_str());
try {
if (cmd->current_proc) {
cmd->current_proc->kill();
cmd->current_proc->wait();
}
cmd->current_proc = std::make_unique<ChildProcess>(args, std::move(opts));
} catch (const std::exception& exc) {
log(ERR,
"trigger ",
root->root_path,
":",
cmd->triggername,
" failed: ",
exc.what(),
"\n");
}
// We have integration tests that check for this string
log(cmd->current_proc ? DBG : ERR, "posix_spawnp: ", cmd->triggername, "\n");
}