Exemple #1
0
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;
}
Exemple #2
0
// 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");
  }
}
Exemple #3
0
// 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");
}
Exemple #4
0
// 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")});
}
Exemple #5
0
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;
}
Exemple #6
0
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;
}
Exemple #7
0
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;
}
Exemple #8
0
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;
}
Exemple #9
0
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;
}
Exemple #10
0
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;
}
Exemple #11
0
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;
}
Exemple #12
0
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;
}
Exemple #13
0
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;
}
Exemple #14
0
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;
}
Exemple #15
0
/**
 * 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;
}
Exemple #16
0
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);
}
Exemple #17
0
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;
}
Exemple #18
0
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));
  }
}
Exemple #19
0
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);
      }
    }
  }
}