Exemplo n.º 1
0
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;
}
Exemplo n.º 2
0
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);
}