void spawn_command()

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");
}