w_string_t *w_string_slice(w_string_t *str, uint32_t start, uint32_t len) { w_string_t *slice; if (start == 0 && len == str->len) { w_string_addref(str); return str; } if (start >= str->len || start + len > str->len) { errno = EINVAL; w_log(W_LOG_FATAL, "illegal string slice start=%" PRIu32 " len=%" PRIu32 " but str->len=%" PRIu32 "\n", start, len, str->len); return NULL; } slice = calloc(1, sizeof(*str)); slice->refcnt = 1; slice->len = len; slice->buf = str->buf + start; slice->slice = str; slice->hval = w_hash_bytes(slice->buf, slice->len, 0); w_string_addref(str); return slice; }
w_string_t *w_string_slice(w_string_t *str, uint32_t start, uint32_t len) { if (start == 0 && len == str->len) { w_string_addref(str); return str; } if (start > str->len || start + len > str->len) { errno = EINVAL; throw std::range_error("illegal string slice"); } // Can't just new w_string_t because the delref has to call delete[] // in most cases. auto slice = (w_string_t*)(new char[sizeof(w_string_t)]); new (slice) watchman_string(); slice->refcnt = 1; slice->len = len; slice->buf = str->buf + start; slice->slice = str; slice->type = str->type; w_string_addref(str); return slice; }
/* return a reference to a lowercased version of a string */ w_string_t *w_string_dup_lower(w_string_t *str) { bool is_lower = true; char *buf; uint32_t i; w_string_t *s; for (i = 0; i < str->len; i++) { if (tolower((uint8_t)str->buf[i]) != str->buf[i]) { is_lower = false; break; } } if (is_lower) { w_string_addref(str); return str; } /* need to make a lowercase version */ s = (w_string_t*)(new char[sizeof(*s) + str->len + 1]); new (s) watchman_string(); s->refcnt = 1; s->len = str->len; buf = (char*)(s + 1); for (i = 0; i < str->len; i++) { buf[i] = (char)tolower((uint8_t)str->buf[i]); } buf[str->len] = 0; s->buf = buf; return s; }
w_string_t *w_string_path_cat(w_string_t *parent, w_string_t *rhs) { w_string_t *s; int len; char *buf; if (rhs->len == 0) { w_string_addref(parent); return parent; } len = parent->len + rhs->len + 1; s = malloc(sizeof(*s) + len + 1); if (!s) { perror("no memory available"); abort(); } s->refcnt = 1; s->len = len; s->slice = NULL; buf = (char*)(s + 1); memcpy(buf, parent->buf, parent->len); buf[parent->len] = '/'; memcpy(buf + parent->len + 1, rhs->buf, rhs->len); buf[parent->len + 1 + rhs->len] = '\0'; s->buf = buf; s->hval = w_hash_bytes(buf, len, 0); return s; }
w_string_t *w_string_path_cat_cstr(w_string_t *parent, const char *rhs) { w_string_t *s; int len; char *buf; uint32_t rhs_len = u32_strlen(rhs); if (rhs_len == 0) { w_string_addref(parent); return parent; } len = parent->len + rhs_len + 1; s = malloc(sizeof(*s) + len + 1); if (!s) { perror("no memory available"); abort(); } s->refcnt = 1; s->len = len; s->slice = NULL; buf = (char*)(s + 1); memcpy(buf, parent->buf, parent->len); buf[parent->len] = WATCHMAN_DIR_SEP; memcpy(buf + parent->len + 1, rhs, rhs_len); buf[parent->len + 1 + rhs_len] = '\0'; s->buf = buf; s->hval = w_hash_bytes(buf, len, 0); return s; }
w_string_t *w_string_path_cat_cstr_len(w_string_t *parent, const char *rhs, uint32_t rhs_len) { w_string_t *s; int len; char *buf; if (rhs_len == 0) { w_string_addref(parent); return parent; } len = parent->len + rhs_len + 1; s = (w_string_t*)(new char[sizeof(*s) + len + 1]); new (s) watchman_string(); s->refcnt = 1; s->len = len; buf = (char*)(s + 1); memcpy(buf, parent->buf, parent->len); buf[parent->len] = '/'; memcpy(buf + parent->len + 1, rhs, rhs_len); buf[parent->len + 1 + rhs_len] = '\0'; s->buf = buf; s->type = parent->type; return s; }
static w_ht_val_t copy_pending(w_ht_val_t key) { struct pending_move *src = w_ht_val_ptr(key); struct pending_move *dest = malloc(sizeof(*dest)); dest->created = src->created; dest->name = src->name; w_string_addref(src->name); return w_ht_ptr_val(dest); }
w_string_t *w_string_canon_path(w_string_t *str) { int end; int trim = 0; for (end = str->len - 1; end >= 0 && str->buf[end] == '/'; end--) { trim++; } if (trim) { return w_string_slice(str, 0, str->len - trim); } w_string_addref(str); return str; }
w_string_t *w_string_path_cat(w_string_t *parent, w_string_t *rhs) { char name_buf[WATCHMAN_NAME_MAX]; if (rhs->len == 0) { w_string_addref(parent); return parent; } snprintf(name_buf, sizeof(name_buf), "%.*s/%.*s", parent->len, parent->buf, rhs->len, rhs->buf); return w_string_new(name_buf); }
// Normalize directory separators to match the platform. // Also trims any trailing directory separators w_string_t *w_string_normalize_separators(w_string_t *str, char target_sep) { w_string_t *s; char *buf; uint32_t i, len; len = str->len; if (len == 0) { w_string_addref(str); return str; } // This doesn't do any special UNC or path len escape prefix handling // on windows. We don't currently use it in a way that would require it. // Trim any trailing dir seps while (len > 0) { if (str->buf[len-1] == '/' || str->buf[len-1] == '\\') { --len; } else { break; } } s = malloc(sizeof(*s) + len + 1); if (!s) { perror("no memory available"); abort(); } s->refcnt = 1; s->len = len; s->slice = NULL; buf = (char*)(s + 1); for (i = 0; i < len; i++) { if (str->buf[i] == '/' || str->buf[i] == '\\') { buf[i] = target_sep; } else { buf[i] = str->buf[i]; } } buf[len] = 0; s->buf = buf; s->hval = w_hash_bytes(buf, len, 0); return s; }
w_string_t *w_string_basename(w_string_t *str) { int end; /* can't use libc strXXX functions because we may be operating * on a slice */ for (end = str->len - 1; end >= 0; end--) { if (str->buf[end] == '/') { /* found the end of the parent dir */ return w_string_slice(str, end + 1, str->len - (end + 1)); } } w_string_addref(str); return str; }
w_string& w_string::operator=(const w_string& other) { if (&other == this) { return *this; } reset(); if (str_) { w_string_delref(str_); } str_ = other.str_; if (str_) { w_string_addref(str_); } return *this; }
// Normalize directory separators to match the platform. // Also trims any trailing directory separators w_string_t *w_string_normalize_separators(w_string_t *str, char target_sep) { w_string_t *s; char *buf; uint32_t i, len; len = str->len; if (len == 0) { w_string_addref(str); return str; } // This doesn't do any special UNC or path len escape prefix handling // on windows. We don't currently use it in a way that would require it. // Trim any trailing dir seps while (len > 0) { if (str->buf[len-1] == '/' || str->buf[len-1] == '\\') { --len; } else { break; } } s = (w_string_t*)(new char[sizeof(*s) + len + 1]); new (s) watchman_string(); s->refcnt = 1; s->len = len; buf = (char*)(s + 1); for (i = 0; i < len; i++) { if (str->buf[i] == '/' || str->buf[i] == '\\') { buf[i] = target_sep; } else { buf[i] = str->buf[i]; } } buf[len] = 0; s->buf = buf; return s; }
/* return a reference to a lowercased version of a string */ w_string_t *w_string_dup_lower(w_string_t *str) { bool is_lower = true; char *buf; uint32_t i; w_string_t *s; for (i = 0; i < str->len; i++) { if (tolower(str->buf[i]) != str->buf[i]) { is_lower = false; break; } } if (is_lower) { w_string_addref(str); return str; } /* need to make a lowercase version */ s = malloc(sizeof(*s) + str->len + 1); if (!s) { perror("no memory available"); abort(); } s->refcnt = 1; s->len = str->len; s->slice = NULL; buf = (char*)(s + 1); for (i = 0; i < str->len; i++) { buf[i] = tolower(str->buf[i]); } buf[str->len] = 0; s->buf = buf; s->hval = w_hash_bytes(buf, str->len, 0); return s; }
w_string::w_string(w_string_t* str, bool addRef) : str_(str) { if (str_ && addRef) { w_string_addref(str_); } }
static bool kqueue_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct kqueue_root_state *state = root->watch; int n; int i; struct timespec ts = { 0, 0 }; struct timeval now; unused_parameter(watcher); errno = 0; n = kevent(state->kq_fd, NULL, 0, state->keventbuf, sizeof(state->keventbuf) / sizeof(state->keventbuf[0]), &ts); w_log(W_LOG_DBG, "consume_kqueue: %s n=%d err=%s\n", root->root_path->buf, n, strerror(errno)); if (root->cancelled) { return 0; } gettimeofday(&now, NULL); for (i = 0; n > 0 && i < n; i++) { uint32_t fflags = state->keventbuf[i].fflags; bool is_dir = IS_DIR_BIT_SET(state->keventbuf[i].udata); w_string_t *path; char flags_label[128]; int fd = state->keventbuf[i].ident; w_expand_flags(kflags, fflags, flags_label, sizeof(flags_label)); pthread_mutex_lock(&state->lock); path = w_ht_val_ptr(w_ht_get(state->fd_to_name, fd)); if (!path) { // Was likely a buffered notification for something that we decided // to stop watching w_log(W_LOG_DBG, " KQ notif for fd=%d; flags=0x%x %s no ref for it in fd_to_name\n", fd, fflags, flags_label); pthread_mutex_unlock(&state->lock); continue; } w_string_addref(path); w_log(W_LOG_DBG, " KQ fd=%d path %s [0x%x %s]\n", fd, path->buf, fflags, flags_label); if ((fflags & (NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE))) { struct kevent k; if (w_string_equal(path, root->root_path)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved [code 0x%x], canceling watch\n", root->root_path->buf, fflags); w_root_cancel(root); pthread_mutex_unlock(&state->lock); return 0; } // Remove our watch bits memset(&k, 0, sizeof(k)); EV_SET(&k, fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); kevent(state->kq_fd, &k, 1, NULL, 0, 0); w_ht_del(state->name_to_fd, w_ht_ptr_val(path)); w_ht_del(state->fd_to_name, fd); } pthread_mutex_unlock(&state->lock); w_pending_coll_add(coll, path, !is_dir, now, !is_dir); w_string_delref(path); } return n > 0; }
w_string::w_string(const w_string& other) : str_(other.str_) { if (str_) { w_string_addref(str_); } }
static void spawn_command(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; w_stm_t stdin_file = NULL; json_t *args; 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; char clockbuf[128]; 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; stdin_file = prepare_stdin(cmd, res); if (!stdin_file) { w_log(W_LOG_ERR, "trigger %.*s:%s %s\n", (int)root->root_path->len, root->root_path->buf, cmd->triggername->buf, 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. if (cmd->max_files_stdin > 0 && res->num_results > cmd->max_files_stdin) { file_overflow = true; } // 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 && clock_id_string(since_spec->clock.root_number, since_spec->clock.ticks, clockbuf, sizeof(clockbuf))) { w_envp_set_cstring(cmd->envht, "WATCHMAN_SINCE", clockbuf); } else { w_envp_unset(cmd->envht, "WATCHMAN_SINCE"); } if (clock_id_string(res->root_number, res->ticks, clockbuf, sizeof(clockbuf))) { w_envp_set_cstring(cmd->envht, "WATCHMAN_CLOCK", clockbuf); } else { w_envp_unset(cmd->envht, "WATCHMAN_CLOCK"); } 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 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 (i = 0; i < res->num_results; i++) { // also: NUL terminator and entry in argv uint32_t size = res->results[i].relname->len + 1 + sizeof(char*); if (argspace_remaining < size) { file_overflow = true; break; } argspace_remaining -= size; json_array_append_new( args, json_string_nocheck(res->results[i].relname->buf) ); } } argv = w_argv_copy_from_json(args, 0); json_decref(args); args = NULL; 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, w_stm_fileno(stdin_file), STDIN_FILENO); #else posix_spawn_file_actions_adddup2_handle_np(&actions, w_stm_handle(stdin_file), 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(cwd); if (w_is_path_absolute(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); } pthread_mutex_lock(&spawn_lock); #ifndef _WIN32 ignore_result(chdir(working_dir->buf)); #else posix_spawnattr_setcwd_np(&attr, working_dir->buf); #endif w_string_delref(working_dir); working_dir = NULL; ret = posix_spawnp(&cmd->current_proc, argv[0], &actions, &attr, argv, envp); if (ret == 0) { w_root_addref(root); insert_running_pid(cmd->current_proc, root); } else { // 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 pthread_mutex_unlock(&spawn_lock); // 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:\n"); 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", (int)root->root_path->len, root->root_path->buf, cmd->triggername->buf, (int)cmd->current_proc, ret, strerror(ret)); free(argv); free(envp); posix_spawnattr_destroy(&attr); posix_spawn_file_actions_destroy(&actions); if (stdin_file) { w_stm_close(stdin_file); } }
static void process_inotify_event( w_root_t *root, struct watchman_pending_collection *coll, struct inotify_event *ine, struct timeval now) { struct inot_root_state *state = root->watch; char flags_label[128]; w_expand_flags(inflags, ine->mask, flags_label, sizeof(flags_label)); w_log(W_LOG_DBG, "notify: wd=%d mask=0x%x %s %s\n", ine->wd, ine->mask, flags_label, ine->len > 0 ? ine->name : ""); if (ine->wd == -1 && (ine->mask & IN_Q_OVERFLOW)) { /* we missed something, will need to re-crawl */ w_root_schedule_recrawl(root, "IN_Q_OVERFLOW"); } else if (ine->wd != -1) { w_string_t *dir_name = NULL; w_string_t *name = NULL; char buf[WATCHMAN_NAME_MAX]; int pending_flags = W_PENDING_VIA_NOTIFY; pthread_mutex_lock(&state->lock); dir_name = w_ht_val_ptr(w_ht_get(state->wd_to_name, ine->wd)); if (dir_name) { w_string_addref(dir_name); } pthread_mutex_unlock(&state->lock); if (dir_name) { if (ine->len > 0) { snprintf(buf, sizeof(buf), "%.*s/%s", dir_name->len, dir_name->buf, ine->name); name = w_string_new(buf); } else { name = dir_name; w_string_addref(name); } } if (ine->len > 0 && (ine->mask & (IN_MOVED_FROM|IN_ISDIR)) == (IN_MOVED_FROM|IN_ISDIR)) { struct pending_move mv; // record this as a pending move, so that we can automatically // watch the target when we get the other side of it. mv.created = now.tv_sec; mv.name = name; pthread_mutex_lock(&state->lock); if (!w_ht_replace(state->move_map, ine->cookie, w_ht_ptr_val(&mv))) { w_log(W_LOG_FATAL, "failed to store %" PRIx32 " -> %s in move map\n", ine->cookie, name->buf); } pthread_mutex_unlock(&state->lock); w_log(W_LOG_DBG, "recording move_from %" PRIx32 " %s\n", ine->cookie, name->buf); } if (ine->len > 0 && (ine->mask & (IN_MOVED_TO|IN_ISDIR)) == (IN_MOVED_FROM|IN_ISDIR)) { struct pending_move *old; pthread_mutex_lock(&state->lock); old = w_ht_val_ptr(w_ht_get(state->move_map, ine->cookie)); if (old) { int wd = inotify_add_watch(state->infd, name->buf, WATCHMAN_INOTIFY_MASK); if (wd == -1) { if (errno == ENOSPC || errno == ENOMEM) { // Limits exceeded, no recovery from our perspective set_poison_state(root, name, now, "inotify-add-watch", errno, inot_strerror(errno)); } else { w_log(W_LOG_DBG, "add_watch: %s %s\n", name->buf, inot_strerror(errno)); } } else { w_log(W_LOG_DBG, "moved %s -> %s\n", old->name->buf, name->buf); w_ht_replace(state->wd_to_name, wd, w_ht_ptr_val(name)); } } else { w_log(W_LOG_DBG, "move: cookie=%" PRIx32 " not found in move map %s\n", ine->cookie, name->buf); } pthread_mutex_unlock(&state->lock); } if (dir_name) { if ((ine->mask & (IN_UNMOUNT|IN_IGNORED|IN_DELETE_SELF|IN_MOVE_SELF))) { w_string_t *pname; if (w_string_equal(root->root_path, name)) { w_log(W_LOG_ERR, "root dir %s has been (re)moved, canceling watch\n", root->root_path->buf); w_string_delref(name); w_string_delref(dir_name); w_root_cancel(root); return; } // We need to examine the parent and crawl down pname = w_string_dirname(name); w_log(W_LOG_DBG, "mask=%x, focus on parent: %.*s\n", ine->mask, pname->len, pname->buf); w_string_delref(name); name = pname; pending_flags |= W_PENDING_RECURSIVE; } if (ine->mask & (IN_CREATE|IN_DELETE)) { pending_flags |= W_PENDING_RECURSIVE; } w_log(W_LOG_DBG, "add_pending for inotify mask=%x %.*s\n", ine->mask, name->len, name->buf); w_pending_coll_add(coll, name, now, pending_flags); w_string_delref(name); // The kernel removed the wd -> name mapping, so let's update // our state here also if ((ine->mask & IN_IGNORED) != 0) { w_log(W_LOG_DBG, "mask=%x: remove watch %d %.*s\n", ine->mask, ine->wd, dir_name->len, dir_name->buf); pthread_mutex_lock(&state->lock); w_ht_del(state->wd_to_name, ine->wd); pthread_mutex_unlock(&state->lock); } w_string_delref(dir_name); } else if ((ine->mask & (IN_MOVE_SELF|IN_IGNORED)) == 0) { // If we can't resolve the dir, and this isn't notification // that it has gone away, then we want to recrawl to fix // up our state. w_log(W_LOG_ERR, "wanted dir %d for mask %x but not found %.*s\n", ine->wd, ine->mask, ine->len, ine->name); w_root_schedule_recrawl(root, "dir missing from internal state"); } } }
bool w_query_process_file( w_query *query, struct w_query_ctx *ctx, struct watchman_file *file) { struct watchman_rule_match *m; if (ctx->wholename) { w_string_delref(ctx->wholename); ctx->wholename = NULL; } ctx->file = file; // For fresh instances, only return files that currently exist. if (!ctx->since.is_timestamp && ctx->since.clock.is_fresh_instance && !file->exists) { return true; } // We produce an output for this file if there is no expression, // or if the expression matched. if (query->expr && !w_query_expr_evaluate(query->expr, ctx, file)) { // No matched return true; } // Need more room? if (ctx->num_results + 1 > ctx->num_allocd) { uint32_t new_num = ctx->num_allocd ? ctx->num_allocd * 2 : 64; struct watchman_rule_match *res; res = realloc(ctx->results, new_num * sizeof(*res)); if (!res) { w_log(W_LOG_ERR, "out of memory while capturing matches!\n"); return false; } ctx->results = res; ctx->num_allocd = new_num; } m = &ctx->results[ctx->num_results++]; m->root_number = ctx->root->number; m->relname = w_query_ctx_get_wholename(ctx); if (!m->relname) { w_log(W_LOG_ERR, "out of memory while capturing matches!\n"); return false; } w_string_addref(m->relname); m->file = file; if (ctx->since.is_timestamp) { m->is_new = w_timeval_compare(ctx->since.timestamp, file->ctime.tv) > 0; } else if (ctx->since.clock.is_fresh_instance) { m->is_new = true; } else { m->is_new = file->ctime.ticks > ctx->since.clock.ticks; } return true; }
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); }