static void accept_loop(FileDescriptor&& listenerDescriptor) { auto listener = w_stm_fdopen(std::move(listenerDescriptor)); while (!stopping) { FileDescriptor client_fd; struct watchman_event_poll pfd[2]; int bufsize; #ifdef HAVE_LIBGIMLI_H if (hb) { gimli_heartbeat_set(hb, GIMLI_HB_RUNNING); } #endif pfd[0].evt = listener->getEvents(); pfd[1].evt = listener_thread_event.get(); if (w_poll_events(pfd, 2, 60000) == 0) { if (stopping) { break; } // Timed out, or error. // Arrange to sanity check that we're working w_check_my_sock(); continue; } if (stopping) { break; } #ifdef HAVE_ACCEPT4 client_fd = FileDescriptor( accept4(listener->getFileDescriptor().fd(), nullptr, 0, SOCK_CLOEXEC)); #else client_fd = FileDescriptor(accept(listener->getFileDescriptor().fd(), nullptr, 0)); #endif if (!client_fd) { continue; } client_fd.setCloExec(); bufsize = WATCHMAN_IO_BUF_SIZE; setsockopt( client_fd.fd(), SOL_SOCKET, SO_SNDBUF, (void*)&bufsize, sizeof(bufsize)); make_new_client(w_stm_fdopen(std::move(client_fd))); } }
bool w_cmd_realpath_root(json_ref& args, char** errmsg) { const char *path; if (json_array_size(args) < 2) { ignore_result(asprintf(errmsg, "wrong number of arguments")); return false; } path = json_string_value(json_array_get(args, 1)); if (!path) { ignore_result(asprintf(errmsg, "second argument must be a string")); return false; } try { auto resolved = realPath(path); args.array()[1] = w_string_to_json(resolved); return true; } catch (const std::exception &exc) { watchman::log(watchman::DBG, "w_cmd_realpath_root: path ", path, " does not resolve: ", exc.what(), "\n"); // We don't treat this as an error; the caller will subsequently // fail and perform their usual error handling return true; } }
// If we are running under inetd-style supervision, call this function // to move the inetd provided socket descriptor(s) to a new descriptor // number and remember that we can just use these when we're starting // up the listener. void w_listener_prep_inetd() { if (listener_fd) { throw std::runtime_error( "w_listener_prep_inetd: listener_fd is already assigned"); } listener_fd = FileDescriptor(dup(STDIN_FILENO), "dup(stdin) for listener"); }
static FileDescriptor create_pipe_server(const char* path) { return FileDescriptor(intptr_t(CreateNamedPipe( path, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, WATCHMAN_IO_BUF_SIZE, 512, 0, nullptr))); }
static void execute_common( struct w_query_ctx* ctx, w_perf_t* sample, w_query_res* res, w_query_generator generator) { if (ctx->query->dedup_results) { ctx->dedup.reserve(64); } res->is_fresh_instance = !ctx->since.is_timestamp && ctx->since.clock.is_fresh_instance; if (!(res->is_fresh_instance && ctx->query->empty_on_fresh_instance)) { if (!generator) { generator = default_generators; } generator(ctx->query, ctx->root, ctx); } if (!ctx->resultsToRender.empty()) { collectAll(ctx->resultsToRender.begin(), ctx->resultsToRender.end()) .then([&](Result<std::vector<Result<json_ref>>>&& results) { auto& vec = results.value(); for (auto& item : vec) { json_array_append_new(ctx->resultsArray, std::move(item.value())); } }) .get(); } if (sample && sample->finish()) { sample->add_root_meta(ctx->root); sample->add_meta( "query_execute", json_object( {{"fresh_instance", json_boolean(res->is_fresh_instance)}, {"num_deduped", json_integer(ctx->num_deduped)}, {"num_results", json_integer(json_array_size(ctx->resultsArray))}, {"num_walked", json_integer(ctx->getNumWalked())}, {"query", ctx->query->query_spec}})); sample->log(); } res->resultsArray = ctx->resultsArray; res->dedupedFileNames = std::move(ctx->dedup); }
// Given a target of the form "absolute_path/filename", return // realpath(absolute_path) + filename, where realpath(absolute_path) resolves // all the symlinks in absolute_path. static w_string get_normalized_target(const w_string& target) { int err; w_assert( w_string_path_is_absolute(target), "get_normalized_target: path %s is not absolute\n", target.c_str()); auto dir_name = target.dirName(); auto dir_name_real = realPath(dir_name.c_str()); err = errno; if (dir_name_real) { auto file_name = target.baseName(); return w_string::pathCat({dir_name_real, file_name}); } errno = err; return nullptr; }
// For watch-project, take a root path string and resolve the // containing project directory, then update the args to reflect // that path. // relpath will hold the path to the project dir, relative to the // watched dir. If it is NULL it means that the project dir is // equivalent to the watched dir. static w_string resolve_projpath(const json_ref& args, char** errmsg, w_string& relpath) { const char* path; bool enforcing; if (json_array_size(args) < 2) { ignore_result(asprintf(errmsg, "wrong number of arguments")); return nullptr; } path = json_string_value(json_array_get(args, 1)); if (!path) { ignore_result(asprintf(errmsg, "second argument must be a string")); return nullptr; } auto resolved = realPath(path); auto root_files = cfg_compute_root_files(&enforcing); if (!root_files) { ignore_result(asprintf(errmsg, "resolve_projpath: error computing root_files configuration value, " "consult your log file at %s for more details", log_name)); return nullptr; } // See if we're requesting something in a pre-existing watch w_string_piece prefix; w_string_piece relpiece; if (findEnclosingRoot(resolved, prefix, relpiece)) { relpath = relpiece.asWString(); resolved = prefix.asWString(); json_array_set_new(args, 1, w_string_to_json(resolved)); return resolved; } auto resolvedpiece = resolved.piece(); if (find_project_root(root_files, resolvedpiece, relpiece)) { relpath = relpiece.asWString(); resolved = resolvedpiece.asWString(); json_array_set_new(args, 1, w_string_to_json(resolved)); return resolved; } if (!enforcing) { // We'll use the path they originally requested return resolved; } // Convert root files to comma delimited string for error message auto root_files_list = cfg_pretty_print_root_files(root_files); ignore_result(asprintf( errmsg, "resolve_projpath: None of the files listed in global config " "root_files are present in path `%s` or any of its " "parent directories. root_files is defined by the " "`%s` config file and includes %s. " "One or more of these files must be present in order to allow " "a watch. Try pulling and checking out a newer version of the project?", path, cfg_get_global_config_file_path().c_str(), root_files_list.c_str())); return nullptr; }
static FileDescriptor get_listener_socket(const char *path) { struct sockaddr_un un; mode_t perms = cfg_get_perms( "sock_access", true /* write bits */, false /* execute bits */); FileDescriptor listener_fd; #ifdef __APPLE__ listener_fd = w_get_listener_socket_from_launchd(); if (listener_fd) { w_log(W_LOG_ERR, "Using socket from launchd as listening socket\n"); return listener_fd; } #endif if (strlen(path) >= sizeof(un.sun_path) - 1) { w_log(W_LOG_ERR, "%s: path is too long\n", path); return FileDescriptor(); } listener_fd = FileDescriptor(socket(PF_LOCAL, SOCK_STREAM, 0), "socket"); un.sun_family = PF_LOCAL; memcpy(un.sun_path, path, strlen(path) + 1); unlink(path); if (bind(listener_fd.fd(), (struct sockaddr*)&un, sizeof(un)) != 0) { w_log(W_LOG_ERR, "bind(%s): %s\n", path, strerror(errno)); return FileDescriptor(); } // The permissions in the containing directory should be correct, so this // should be correct as well. But set the permissions in any case. if (chmod(path, perms) == -1) { w_log(W_LOG_ERR, "chmod(%s, %#o): %s", path, perms, strerror(errno)); return FileDescriptor(); } // Double-check that the socket has the right permissions. This can happen // when the containing directory was created in a previous run, with a group // the user is no longer in. struct stat st; if (lstat(path, &st) == -1) { watchman::log(watchman::ERR, "lstat(", path, "): ", strerror(errno), "\n"); return FileDescriptor(); } // This is for testing only // (test_sock_perms.py:test_user_previously_in_sock_group). Do not document. const char *sock_group_name = cfg_get_string("__sock_file_group", nullptr); if (!sock_group_name) { sock_group_name = cfg_get_string("sock_group", nullptr); } if (sock_group_name) { const struct group *sock_group = w_get_group(sock_group_name); if (!sock_group) { return FileDescriptor(); } if (st.st_gid != sock_group->gr_gid) { watchman::log( watchman::ERR, "for socket '", path, "', gid ", st.st_gid, " doesn't match expected gid ", sock_group->gr_gid, " (group name ", sock_group_name, "). Ensure that you are still a member of group ", sock_group_name, ".\n"); return FileDescriptor(); } } if (listen(listener_fd.fd(), 200) != 0) { w_log(W_LOG_ERR, "listen(%s): %s\n", path, strerror(errno)); return FileDescriptor(); } return listener_fd; }
static void named_pipe_accept_loop_internal(const char* path) { HANDLE handles[2]; auto olap = OVERLAPPED(); HANDLE connected_event = CreateEvent(NULL, FALSE, TRUE, NULL); if (!connected_event) { w_log( W_LOG_ERR, "named_pipe_accept_loop_internal: CreateEvent failed: %s\n", win32_strerror(GetLastError())); return; } handles[0] = connected_event; handles[1] = listener_thread_event; olap.hEvent = connected_event; w_log(W_LOG_ERR, "waiting for pipe clients on %s\n", path); while (!stopping) { FileDescriptor client_fd; DWORD res; client_fd = FileDescriptor(intptr_t(CreateNamedPipe( path, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, WATCHMAN_IO_BUF_SIZE, 512, 0, nullptr))); if (!client_fd) { w_log(W_LOG_ERR, "CreateNamedPipe(%s) failed: %s\n", path, win32_strerror(GetLastError())); continue; } ResetEvent(connected_event); if (!ConnectNamedPipe((HANDLE)client_fd.handle(), &olap)) { res = GetLastError(); if (res == ERROR_PIPE_CONNECTED) { make_new_client(w_stm_fdopen(std::move(client_fd))); continue; } if (res != ERROR_IO_PENDING) { w_log(W_LOG_ERR, "ConnectNamedPipe: %s\n", win32_strerror(GetLastError())); continue; } res = WaitForMultipleObjectsEx(2, handles, false, INFINITE, true); if (res == WAIT_OBJECT_0 + 1) { // Signalled to stop CancelIoEx((HANDLE)client_fd.handle(), &olap); continue; } if (res != WAIT_OBJECT_0) { w_log( W_LOG_ERR, "WaitForMultipleObjectsEx: ConnectNamedPipe: " "unexpected status %u\n", res); CancelIoEx((HANDLE)client_fd.handle(), &olap); continue; } } make_new_client(w_stm_fdopen(std::move(client_fd))); } }
KQueueWatcher::KQueueWatcher(w_root_t* root) : Watcher("kqueue", 0), maps_(maps(root->config.getInt(CFG_HINT_NUM_DIRS, HINT_NUM_DIRS))) { kq_fd = FileDescriptor(kqueue(), "kqueue"); kq_fd.setCloExec(); }