static bool parse_relative_root(w_root_t *root, w_query *res, json_t *query) { json_t *relative_root; w_string_t *path, *canon_path; relative_root = json_object_get(query, "relative_root"); if (!relative_root) { return true; } if (!json_is_string(relative_root)) { res->errmsg = strdup("'relative_root' must be a string"); return false; } path = w_string_new(json_string_value(relative_root)); canon_path = w_string_canon_path(path); res->relative_root = w_string_path_cat(root->root_path, canon_path); res->relative_root_slash = w_string_make_printf("%.*s%c", res->relative_root->len, res->relative_root->buf, WATCHMAN_DIR_SEP); w_string_delref(path); w_string_delref(canon_path); return true; }
w_string_t *w_query_ctx_get_wholename( struct w_query_ctx *ctx ) { w_string_t *full_name; uint32_t name_start; if (ctx->wholename) { return ctx->wholename; } if (ctx->query->relative_root != NULL) { // At this point every path should start with the relative root, so this is // legal name_start = ctx->query->relative_root->len + 1; } else { name_start = ctx->root->root_path->len + 1; } full_name = w_string_path_cat(compute_parent_path(ctx, ctx->file), w_file_get_name(ctx->file)); // Record the name relative to the root ctx->wholename = w_string_slice(full_name, name_start, full_name->len - name_start); w_string_delref(full_name); return ctx->wholename; }
static bool portfs_root_consume_notify(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_pending_collection *coll) { struct portfs_root_state *state = root->watch; uint_t i, n; struct timeval now; unused_parameter(watcher); errno = 0; n = 1; if (port_getn(state->port_fd, state->portevents, sizeof(state->portevents) / sizeof(state->portevents[0]), &n, NULL)) { if (errno == EINTR) { return false; } w_log(W_LOG_FATAL, "port_getn: %s\n", strerror(errno)); } w_log(W_LOG_DBG, "port_getn: n=%u\n", n); if (n == 0) { return false; } for (i = 0; i < n; i++) { if (IS_DIR_BIT_SET(state->portevents[i].portev_user)) { struct watchman_dir *dir = DECODE_DIR(state->portevents[i].portev_user); uint32_t pe = state->portevents[i].portev_events; w_log(W_LOG_DBG, "port: dir %.*s [0x%x]\n", dir->path->len, dir->path->buf, pe); if ((pe & (FILE_RENAME_FROM|UNMOUNTED|MOUNTEDOVER|FILE_DELETE)) && w_string_equal(dir->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, pe); w_root_cancel(root); return false; } w_pending_coll_add(coll, dir->path, false, now, true); } else { struct watchman_file *file = state->portevents[i].portev_user; w_string_t *path; path = w_string_path_cat(file->parent->path, file->name); w_pending_coll_add(coll, path, true, now, true); w_log(W_LOG_DBG, "port: file %.*s\n", path->len, path->buf); w_string_delref(path); } } return true; }
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; w_log(W_LOG_DBG, "kqueue(%s)\n", root->root_path->buf); 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; if (IS_DIR_BIT_SET(state->keventbuf[i].udata)) { struct watchman_dir *dir = DECODE_DIR(state->keventbuf[i].udata); w_log(W_LOG_DBG, " KQ dir %s [0x%x]\n", dir->path->buf, fflags); if ((fflags & (NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE)) && w_string_equal(dir->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); return 0; } w_pending_coll_add(coll, dir->path, false, now, false); } else { // NetBSD defines udata as intptr type, so the cast is necessary struct watchman_file *file = (void *)state->keventbuf[i].udata; w_string_t *path; path = w_string_path_cat(file->parent->path, file->name); w_pending_coll_add(coll, path, true, now, true); w_log(W_LOG_DBG, " KQ file %.*s [0x%x]\n", path->len, path->buf, fflags); w_string_delref(path); } } return n > 0; }
static bool kqueue_root_start_watch_file(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_file *file) { struct kqueue_root_state *state = root->watch; struct kevent k; w_ht_val_t fdval; int fd; w_string_t *full_name; unused_parameter(watcher); full_name = w_string_path_cat(file->parent->path, file->name); pthread_mutex_lock(&state->lock); if (w_ht_lookup(state->name_to_fd, w_ht_ptr_val(full_name), &fdval, false)) { // Already watching it pthread_mutex_unlock(&state->lock); return true; } pthread_mutex_unlock(&state->lock); w_log(W_LOG_DBG, "watch_file(%s)\n", full_name->buf); fd = open(full_name->buf, O_EVTONLY|O_CLOEXEC); if (fd == -1) { w_log(W_LOG_ERR, "failed to open %s O_EVTONLY: %s\n", full_name->buf, strerror(errno)); w_string_delref(full_name); return false; } memset(&k, 0, sizeof(k)); EV_SET(&k, fd, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_WRITE|NOTE_DELETE|NOTE_EXTEND|NOTE_RENAME|NOTE_ATTRIB, 0, full_name); pthread_mutex_lock(&state->lock); w_ht_replace(state->name_to_fd, w_ht_ptr_val(full_name), fd); w_ht_replace(state->fd_to_name, fd, w_ht_ptr_val(full_name)); pthread_mutex_unlock(&state->lock); if (kevent(state->kq_fd, &k, 1, NULL, 0, 0)) { w_log(W_LOG_DBG, "kevent EV_ADD file %s failed: %s", full_name->buf, strerror(errno)); close(fd); pthread_mutex_lock(&state->lock); w_ht_del(state->name_to_fd, w_ht_ptr_val(full_name)); w_ht_del(state->fd_to_name, fd); pthread_mutex_unlock(&state->lock); } else { w_log(W_LOG_DBG, "kevent file %s -> %d\n", full_name->buf, fd); } w_string_delref(full_name); return true; }
w_string_t *w_query_ctx_get_wholename( struct w_query_ctx *ctx ) { w_string_t *full_name; uint32_t name_start; if (ctx->wholename) { return ctx->wholename; } name_start = ctx->root->root_path->len + 1; full_name = w_string_path_cat(ctx->file->parent->path, ctx->file->name); // Record the name relative to the root ctx->wholename = w_string_slice(full_name, name_start, full_name->len - name_start); w_string_delref(full_name); return ctx->wholename; }
static void *readchanges_thread(void *arg) { w_root_t *root = arg; struct winwatch_root_state *state = root->watch; DWORD size = WATCHMAN_BATCH_LIMIT * (sizeof(FILE_NOTIFY_INFORMATION) + 512); char *buf; DWORD err, filter; OVERLAPPED olap; BOOL initiate_read = true; HANDLE handles[2] = { state->olap, state->ping }; DWORD bytes; w_set_thread_name("readchange %.*s", root->root_path->len, root->root_path->buf); // Block until winmatch_root_st is waiting for our initialization pthread_mutex_lock(&state->mtx); filter = FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME| FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_SIZE| FILE_NOTIFY_CHANGE_LAST_WRITE; memset(&olap, 0, sizeof(olap)); olap.hEvent = state->olap; buf = malloc(size); if (!buf) { w_log(W_LOG_ERR, "failed to allocate %u bytes for dirchanges buf\n", size); goto out; } if (!ReadDirectoryChangesW(state->dir_handle, buf, size, TRUE, filter, NULL, &olap, NULL)) { err = GetLastError(); w_log(W_LOG_ERR, "ReadDirectoryChangesW: failed, cancel watch. %s\n", win32_strerror(err)); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); goto out; } // Signal that we are done with init. We MUST do this AFTER our first // successful ReadDirectoryChangesW, otherwise there is a race condition // where we'll miss observing the cookie for a query that comes in // after we've crawled but before the watch is established. w_log(W_LOG_DBG, "ReadDirectoryChangesW signalling as init done"); pthread_cond_signal(&state->cond); pthread_mutex_unlock(&state->mtx); initiate_read = false; // The state->mutex must not be held when we enter the loop while (!root->cancelled) { if (initiate_read) { if (!ReadDirectoryChangesW(state->dir_handle, buf, size, TRUE, filter, NULL, &olap, NULL)) { err = GetLastError(); w_log(W_LOG_ERR, "ReadDirectoryChangesW: failed, cancel watch. %s\n", win32_strerror(err)); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); break; } else { initiate_read = false; } } w_log(W_LOG_DBG, "waiting for change notifications"); DWORD status = WaitForMultipleObjects(2, handles, FALSE, INFINITE); if (status == WAIT_OBJECT_0) { bytes = 0; if (!GetOverlappedResult(state->dir_handle, &olap, &bytes, FALSE)) { err = GetLastError(); w_log(W_LOG_ERR, "overlapped ReadDirectoryChangesW(%s): 0x%x %s\n", root->root_path->buf, err, win32_strerror(err)); if (err == ERROR_INVALID_PARAMETER && size > NETWORK_BUF_SIZE) { // May be a network buffer related size issue; the docs say that // we can hit this when watching a UNC path. Let's downsize and // retry the read just one time w_log(W_LOG_ERR, "retrying watch for possible network location %s " "with smaller buffer\n", root->root_path->buf); size = NETWORK_BUF_SIZE; initiate_read = true; continue; } if (err == ERROR_NOTIFY_ENUM_DIR) { w_root_schedule_recrawl(root, "ERROR_NOTIFY_ENUM_DIR"); } else { w_log(W_LOG_ERR, "Cancelling watch for %s\n", root->root_path->buf); w_root_lock(root); w_root_cancel(root); w_root_unlock(root); break; } } else { PFILE_NOTIFY_INFORMATION not = (PFILE_NOTIFY_INFORMATION)buf; struct winwatch_changed_item *head = NULL, *tail = NULL; while (true) { struct winwatch_changed_item *item; DWORD n_chars; w_string_t *name, *full; // FileNameLength is in BYTES, but FileName is WCHAR n_chars = not->FileNameLength / sizeof(not->FileName[0]); name = w_string_new_wchar(not->FileName, n_chars); full = w_string_path_cat(root->root_path, name); w_string_delref(name); if (w_is_ignored(root, full->buf, full->len)) { w_string_delref(full); } else { item = calloc(1, sizeof(*item)); item->name = full; if (tail) { tail->next = item; } else { head = item; } tail = item; } // Advance to next item if (not->NextEntryOffset == 0) { break; } not = (PFILE_NOTIFY_INFORMATION)(not->NextEntryOffset + (char*)not); } if (tail) { pthread_mutex_lock(&state->mtx); if (state->tail) { state->tail->next = head; } else { state->head = head; } state->tail = tail; pthread_mutex_unlock(&state->mtx); pthread_cond_signal(&state->cond); } ResetEvent(state->olap); initiate_read = true; } } else if (status == WAIT_OBJECT_0 + 1) { w_log(W_LOG_ERR, "signalled\n"); break; } else { w_log(W_LOG_ERR, "impossible wait status=%d\n", status); break; } } pthread_mutex_lock(&state->mtx); out: // Signal to winwatch_root_start that we're done initializing in // the failure path. We'll also do this after we've completed // the run loop in the success path; it's a spurious wakeup but // harmless and saves us from adding and setting a control flag // in each of the failure `goto` statements. winwatch_root_dtor // will `pthread_join` us before `state` is freed. pthread_cond_signal(&state->cond); pthread_mutex_unlock(&state->mtx); if (buf) { free(buf); } w_log(W_LOG_DBG, "done\n"); w_root_delref(root); return NULL; }
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 bool path_generator( w_query *query, w_root_t *root, struct w_query_ctx *ctx) { struct watchman_file *f; uint32_t i; for (i = 0; i < query->npaths; i++) { struct watchman_dir *dir; w_string_t *dir_name, *file_name, *full_name; // Compose path with root full_name = w_string_path_cat(root->root_path, query->paths[i].name); // special case of root dir itself if (w_string_equal(root->root_path, full_name)) { // dirname on the root is outside the root, which is useless dir = w_root_resolve_dir(root, full_name, false); goto is_dir; } // Ideally, we'd just resolve it directly as a dir and be done. // It's not quite so simple though, because we may resolve a dir // that had been deleted and replaced by a file. // We prefer to resolve the parent and walk down. dir_name = w_string_dirname(full_name); if (!dir_name) { w_string_delref(full_name); continue; } dir = w_root_resolve_dir(root, dir_name, false); w_string_delref(dir_name); if (!dir) { // Doesn't exist, and never has w_string_delref(full_name); continue; } if (dir->files) { file_name = w_string_basename(query->paths[i].name); f = w_ht_val_ptr(w_ht_get(dir->files, w_ht_ptr_val(file_name))); w_string_delref(file_name); // If it's a file (but not an existent dir) if (f && (!f->exists || !S_ISDIR(f->st.st_mode))) { w_string_delref(full_name); if (!w_query_process_file(query, ctx, f)) { return false; } continue; } } // Is it a dir? dir = w_ht_val_ptr(w_ht_get(dir->dirs, w_ht_ptr_val(full_name))); w_string_delref(full_name); is_dir: // We got a dir; process recursively to specified depth if (dir && !dir_generator(query, root, ctx, dir, query->paths[i].depth)) { return false; } } 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); }