// Caller must hold spawn_lock static void insert_running_pid(pid_t pid, w_root_t *root) { if (!running_kids) { running_kids = w_ht_new(2, NULL); } w_ht_set(running_kids, pid, w_ht_ptr_val(root)); }
void w_capability_register(const char *name) { if (!capabilities) { capabilities = w_ht_new(128, &w_ht_string_funcs); } w_ht_set(capabilities, w_ht_ptr_val(w_string_new(name)), true); }
void w_register_command(struct watchman_command_handler_def *defs) { if (!command_funcs) { command_funcs = w_ht_new(16, &w_ht_string_funcs); } w_ht_set(command_funcs, w_ht_ptr_val(w_string_new(defs->name)), w_ht_ptr_val(defs)); }
void register_commands(struct watchman_command_handler_def *defs) { int i; command_funcs = w_ht_new(16, &w_ht_string_funcs); for (i = 0; defs[i].name; i++) { w_ht_set(command_funcs, (w_ht_val_t)w_string_new(defs[i].name), (w_ht_val_t)defs[i].func); } w_query_init_all(); }
void w_register_command(struct watchman_command_handler_def *defs) { char capname[128]; if (!command_funcs) { command_funcs = w_ht_new(16, &w_ht_string_funcs); } w_ht_set(command_funcs, w_ht_ptr_val(w_string_new(defs->name)), w_ht_ptr_val(defs)); snprintf(capname, sizeof(capname), "cmd-%s", defs->name); w_capability_register(capname); }
static struct watchman_client *make_new_client(w_stm_t stm) { struct watchman_client *client; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); client = calloc(1, derived_client_size); if (!client) { pthread_attr_destroy(&attr); return NULL; } client->stm = stm; w_log(W_LOG_DBG, "accepted client:stm=%p\n", client->stm); if (!w_json_buffer_init(&client->reader)) { // FIXME: error handling } if (!w_json_buffer_init(&client->writer)) { // FIXME: error handling } client->ping = w_event_make(); if (!client->ping) { // FIXME: error handling } derived_client_ctor(client); pthread_mutex_lock(&w_client_lock); w_ht_set(clients, w_ht_ptr_val(client), w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); // Start a thread for the client. // We used to use libevent for this, but we have // a low volume of concurrent clients and the json // parse/encode APIs are not easily used in a non-blocking // server architecture. if (pthread_create(&client->thread_handle, &attr, client_thread, client)) { // It didn't work out, sorry! pthread_mutex_lock(&w_client_lock); w_ht_del(clients, w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); client_delete(client); } pthread_attr_destroy(&attr); return client; }
bool w_query_register_expression_parser( const char *term, w_query_expr_parser parser) { w_string_t *name = w_string_new(term); if (!name) { return false; } if (!term_hash) { term_hash = w_ht_new(32, &w_ht_string_funcs); } return w_ht_set(term_hash, w_ht_ptr_val(name), w_ht_ptr_val(parser)); }
bool w_query_register_expression_parser( const char *term, w_query_expr_parser parser) { char capname[128]; w_string_t *name = w_string_new(term); if (!name) { return false; } snprintf(capname, sizeof(capname), "term-%s", term); w_capability_register(capname); if (!term_hash) { term_hash = w_ht_new(32, &w_ht_string_funcs); } return w_ht_set(term_hash, w_ht_ptr_val(name), w_ht_ptr_val(parser)); }
bool w_start_listener(const char *path) { struct sockaddr_un un; pthread_t thr; pthread_attr_t attr; pthread_mutexattr_t mattr; struct sigaction sa; sigset_t sigset; #ifdef HAVE_LIBGIMLI_H volatile struct gimli_heartbeat *hb = NULL; #endif struct timeval tv; void *ignored; int n_clients = 0; listener_thread = pthread_self(); pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&w_client_lock, &mattr); pthread_mutexattr_destroy(&mattr); #ifdef HAVE_LIBGIMLI_H hb = gimli_heartbeat_attach(); #endif #if defined(HAVE_KQUEUE) || defined(HAVE_FSEVENTS) { struct rlimit limit; int mib[2] = { CTL_KERN, #ifdef KERN_MAXFILESPERPROC KERN_MAXFILESPERPROC #else KERN_MAXFILES #endif }; int maxperproc; size_t len; len = sizeof(maxperproc); sysctl(mib, 2, &maxperproc, &len, NULL, 0); getrlimit(RLIMIT_NOFILE, &limit); w_log(W_LOG_ERR, "file limit is %" PRIu64 " kern.maxfilesperproc=%i\n", limit.rlim_cur, maxperproc); if (limit.rlim_cur != RLIM_INFINITY && maxperproc > 0 && limit.rlim_cur < (rlim_t)maxperproc) { limit.rlim_cur = maxperproc; if (setrlimit(RLIMIT_NOFILE, &limit)) { w_log(W_LOG_ERR, "failed to raise limit to %" PRIu64 " (%s).\n", limit.rlim_cur, strerror(errno)); } else { w_log(W_LOG_ERR, "raised file limit to %" PRIu64 "\n", limit.rlim_cur); } } getrlimit(RLIMIT_NOFILE, &limit); #ifndef HAVE_FSEVENTS if (limit.rlim_cur < 10240) { w_log(W_LOG_ERR, "Your file descriptor limit is very low (%" PRIu64 "), " "please consult the watchman docs on raising the limits\n", limit.rlim_cur); } #endif } #endif proc_pid = (int)getpid(); if (gettimeofday(&tv, NULL) == -1) { w_log(W_LOG_ERR, "gettimeofday failed: %s\n", strerror(errno)); return false; } proc_start_time = (uint64_t)tv.tv_sec; if (strlen(path) >= sizeof(un.sun_path) - 1) { w_log(W_LOG_ERR, "%s: path is too long\n", path); return false; } signal(SIGPIPE, SIG_IGN); /* allow SIGUSR1 and SIGCHLD to wake up a blocked thread, without restarting * syscalls */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = wakeme; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); // Block SIGCHLD everywhere sigemptyset(&sigset); sigaddset(&sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &sigset, NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); listener_fd = socket(PF_LOCAL, SOCK_STREAM, 0); if (listener_fd == -1) { w_log(W_LOG_ERR, "socket: %s\n", strerror(errno)); return false; } un.sun_family = PF_LOCAL; strcpy(un.sun_path, path); unlink(path); if (bind(listener_fd, (struct sockaddr*)&un, sizeof(un)) != 0) { w_log(W_LOG_ERR, "bind(%s): %s\n", path, strerror(errno)); close(listener_fd); return false; } if (listen(listener_fd, 200) != 0) { w_log(W_LOG_ERR, "listen(%s): %s\n", path, strerror(errno)); close(listener_fd); return false; } w_set_cloexec(listener_fd); if (pthread_create(&reaper_thread, NULL, child_reaper, NULL)) { w_log(W_LOG_FATAL, "pthread_create(reaper): %s\n", strerror(errno)); return false; } if (!clients) { clients = w_ht_new(2, &client_hash_funcs); } w_state_load(); #ifdef HAVE_LIBGIMLI_H if (hb) { gimli_heartbeat_set(hb, GIMLI_HB_RUNNING); } #endif w_set_nonblock(listener_fd); // Now run the dispatch while (!stopping) { int client_fd; struct watchman_client *client; struct pollfd pfd; int bufsize; #ifdef HAVE_LIBGIMLI_H if (hb) { gimli_heartbeat_set(hb, GIMLI_HB_RUNNING); } #endif pfd.events = POLLIN; pfd.fd = listener_fd; if (poll(&pfd, 1, 10000) < 1 || (pfd.revents & POLLIN) == 0) { continue; } #ifdef HAVE_ACCEPT4 client_fd = accept4(listener_fd, NULL, 0, SOCK_CLOEXEC); #else client_fd = accept(listener_fd, NULL, 0); #endif if (client_fd == -1) { continue; } w_set_cloexec(client_fd); bufsize = WATCHMAN_IO_BUF_SIZE; setsockopt(client_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); client = calloc(1, sizeof(*client)); client->fd = client_fd; w_log(W_LOG_DBG, "accepted client %p fd=%d\n", client, client_fd); if (!w_json_buffer_init(&client->reader)) { // FIXME: error handling } if (!w_json_buffer_init(&client->writer)) { // FIXME: error handling } if (pipe(client->ping)) { // FIXME: error handling } client->subscriptions = w_ht_new(2, &subscription_hash_funcs); w_set_cloexec(client->ping[0]); w_set_nonblock(client->ping[0]); w_set_cloexec(client->ping[1]); w_set_nonblock(client->ping[1]); pthread_mutex_lock(&w_client_lock); w_ht_set(clients, client->fd, w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); // Start a thread for the client. // We used to use libevent for this, but we have // a low volume of concurrent clients and the json // parse/encode APIs are not easily used in a non-blocking // server architecture. if (pthread_create(&thr, &attr, client_thread, client)) { // It didn't work out, sorry! pthread_mutex_lock(&w_client_lock); w_ht_del(clients, client->fd); pthread_mutex_unlock(&w_client_lock); } } pthread_attr_destroy(&attr); /* close out some resources to persuade valgrind to run clean */ close(listener_fd); listener_fd = -1; // Wait for clients, waking any sleeping clients up in the process do { w_ht_iter_t iter; pthread_mutex_lock(&w_client_lock); n_clients = w_ht_size(clients); if (w_ht_first(clients, &iter)) do { struct watchman_client *client = w_ht_val_ptr(iter.value); ignore_result(write(client->ping[1], "a", 1)); } while (w_ht_next(clients, &iter)); pthread_mutex_unlock(&w_client_lock); w_log(W_LOG_ERR, "waiting for %d clients to terminate\n", n_clients); usleep(2000); } while (n_clients > 0); w_root_free_watched_roots(); pthread_join(reaper_thread, &ignored); cfg_shutdown(); return true; }
bool w_start_listener(const char *path) { struct sockaddr_un un; pthread_t thr; pthread_attr_t attr; pthread_mutexattr_t mattr; struct sigaction sa; sigset_t sigset; #ifdef HAVE_LIBGIMLI_H volatile struct gimli_heartbeat *hb = NULL; #endif pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&w_client_lock, &mattr); pthread_mutexattr_destroy(&mattr); #ifdef HAVE_LIBGIMLI_H hb = gimli_heartbeat_attach(); #endif #ifdef HAVE_KQUEUE { struct rlimit limit; int mib[2] = { CTL_KERN, #ifdef KERN_MAXFILESPERPROC KERN_MAXFILESPERPROC #else KERN_MAXFILES #endif }; int maxperproc; size_t len; len = sizeof(maxperproc); sysctl(mib, 2, &maxperproc, &len, NULL, 0); getrlimit(RLIMIT_NOFILE, &limit); w_log(W_LOG_ERR, "file limit is %" PRIu64 " kern.maxfilesperproc=%i\n", limit.rlim_cur, maxperproc); if (limit.rlim_cur != RLIM_INFINITY && maxperproc > 0 && limit.rlim_cur < (rlim_t)maxperproc) { limit.rlim_cur = maxperproc; if (setrlimit(RLIMIT_NOFILE, &limit)) { w_log(W_LOG_ERR, "failed to raise limit to %" PRIu64 " (%s).\n", limit.rlim_cur, strerror(errno)); } else { w_log(W_LOG_ERR, "raised file limit to %" PRIu64 "\n", limit.rlim_cur); } } getrlimit(RLIMIT_NOFILE, &limit); if (limit.rlim_cur < 10240) { w_log(W_LOG_ERR, "Your file descriptor limit is very low (%" PRIu64 "), " "please consult the watchman docs on raising the limits\n", limit.rlim_cur); } } #endif if (strlen(path) >= sizeof(un.sun_path) - 1) { w_log(W_LOG_ERR, "%s: path is too long\n", path); return false; } signal(SIGPIPE, SIG_IGN); /* allow SIGUSR1 and SIGCHLD to wake up a blocked thread, without restarting * syscalls */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = wakeme; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); // Block SIGCHLD everywhere sigemptyset(&sigset); sigaddset(&sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &sigset, NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); listener_fd = socket(PF_LOCAL, SOCK_STREAM, 0); if (listener_fd == -1) { w_log(W_LOG_ERR, "socket: %s\n", strerror(errno)); return false; } un.sun_family = PF_LOCAL; strcpy(un.sun_path, path); if (bind(listener_fd, (struct sockaddr*)&un, sizeof(un)) != 0) { w_log(W_LOG_ERR, "bind(%s): %s\n", path, strerror(errno)); close(listener_fd); return false; } if (listen(listener_fd, 200) != 0) { w_log(W_LOG_ERR, "listen(%s): %s\n", path, strerror(errno)); close(listener_fd); return false; } w_set_cloexec(listener_fd); if (pthread_create(&reaper_thread, NULL, child_reaper, NULL)) { w_log(W_LOG_FATAL, "pthread_create(reaper): %s\n", strerror(errno)); return false; } if (!clients) { clients = w_ht_new(2, &client_hash_funcs); } // Wire up the command handlers register_commands(commands); w_state_load(); #ifdef HAVE_LIBGIMLI_H if (hb) { gimli_heartbeat_set(hb, GIMLI_HB_RUNNING); } w_set_nonblock(listener_fd); #endif // Now run the dispatch while (true) { int client_fd; struct watchman_client *client; struct pollfd pfd; int bufsize; #ifdef HAVE_LIBGIMLI_H if (hb) { gimli_heartbeat_set(hb, GIMLI_HB_RUNNING); } #endif pfd.events = POLLIN; pfd.fd = listener_fd; poll(&pfd, 1, 10000); client_fd = accept(listener_fd, NULL, 0); if (client_fd == -1) { continue; } w_set_cloexec(client_fd); bufsize = WATCHMAN_IO_BUF_SIZE; setsockopt(client_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); client = calloc(1, sizeof(*client)); client->fd = client_fd; if (!w_json_buffer_init(&client->reader)) { // FIXME: error handling } if (!w_json_buffer_init(&client->writer)) { // FIXME: error handling } if (pipe(client->ping)) { // FIXME: error handling } client->subscriptions = w_ht_new(2, &subscription_hash_funcs); w_set_cloexec(client->ping[0]); w_set_nonblock(client->ping[0]); w_set_cloexec(client->ping[1]); w_set_nonblock(client->ping[1]); pthread_mutex_lock(&w_client_lock); w_ht_set(clients, client->fd, (w_ht_val_t)client); pthread_mutex_unlock(&w_client_lock); // Start a thread for the client. // We used to use libevent for this, but we have // a low volume of concurrent clients and the json // parse/encode APIs are not easily used in a non-blocking // server architecture. if (pthread_create(&thr, &attr, client_thread, client)) { // It didn't work out, sorry! pthread_mutex_lock(&w_client_lock); w_ht_del(clients, client->fd); pthread_mutex_unlock(&w_client_lock); } } pthread_attr_destroy(&attr); return true; }
static int posix_spawn_common( bool search_path, pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { STARTUPINFO sinfo; SECURITY_ATTRIBUTES sec; PROCESS_INFORMATION pinfo; char *cmdbuf; char *env_block; DWORD create_flags = CREATE_NO_WINDOW; int ret; int i; unused_parameter(envp); // FIXME cmdbuf = build_command_line(argv); if (!cmdbuf) { return ENOMEM; } env_block = make_env_block(envp); if (!env_block) { free(cmdbuf); return ENOMEM; } memset(&sinfo, 0, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); sinfo.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; sinfo.wShowWindow = SW_HIDE; memset(&sec, 0, sizeof(sec)); sec.nLength = sizeof(sec); sec.bInheritHandle = TRUE; memset(&pinfo, 0, sizeof(pinfo)); if (attrp->flags & POSIX_SPAWN_SETPGROUP) { create_flags |= CREATE_NEW_PROCESS_GROUP; } // Process any dup(2) actions for (i = 0; i < file_actions->ndups; i++) { struct _posix_spawn_file_dup *dup = &file_actions->dups[i]; HANDLE *target = NULL; DWORD err; switch (dup->target_fd) { case 0: target = &sinfo.hStdInput; break; case 1: target = &sinfo.hStdOutput; break; case 2: target = &sinfo.hStdError; break; } if (!target) { w_log(W_LOG_ERR, "posix_spawn: can't target fd outside range [0-2]\n"); ret = ENOSYS; goto done; } if (*target) { CloseHandle(*target); *target = INVALID_HANDLE_VALUE; } if (!DuplicateHandle(GetCurrentProcess(), dup->local_handle, GetCurrentProcess(), target, 0, TRUE, DUPLICATE_SAME_ACCESS)) { err = GetLastError(); w_log(W_LOG_ERR, "posix_spawn: failed to duplicate handle: %s\n", win32_strerror(err)); ret = map_win32_err(err); goto done; } } // Process any file opening actions for (i = 0; i < file_actions->nopens; i++) { struct _posix_spawn_file_open *op = &file_actions->opens[i]; HANDLE h; HANDLE *target = NULL; switch (op->target_fd) { case 0: target = &sinfo.hStdInput; break; case 1: target = &sinfo.hStdOutput; break; case 2: target = &sinfo.hStdError; break; } if (!target) { w_log(W_LOG_ERR, "posix_spawn: can't target fd outside range [0-2]\n"); ret = ENOSYS; goto done; } h = w_handle_open(op->name, op->flags & ~O_CLOEXEC); if (h == INVALID_HANDLE_VALUE) { ret = errno; w_log(W_LOG_ERR, "posix_spawn: failed to open %s:\n", op->name); goto done; } if (*target) { CloseHandle(*target); } *target = h; } if (!sinfo.hStdInput) { sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } if (!sinfo.hStdOutput) { sinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } if (!sinfo.hStdError) { sinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); } if (!CreateProcess(search_path ? NULL : path, cmdbuf, &sec, &sec, TRUE, create_flags, env_block, attrp->working_dir, &sinfo, &pinfo)) { w_log(W_LOG_ERR, "CreateProcess: `%s`: (cwd=%s) %s\n", cmdbuf, attrp->working_dir ? attrp->working_dir : "<process cwd>", win32_strerror(GetLastError())); ret = EACCES; } else { *pid = (pid_t)pinfo.dwProcessId; // Record the pid -> handle mapping for later wait/reap pthread_mutex_lock(&child_proc_lock); if (!child_procs) { child_procs = w_ht_new(2, NULL); } w_ht_set(child_procs, pinfo.dwProcessId, w_ht_ptr_val(pinfo.hProcess)); pthread_mutex_unlock(&child_proc_lock); CloseHandle(pinfo.hThread); ret = 0; } done: free(cmdbuf); free(env_block); // If we manufactured any handles, close them out now if (sinfo.hStdInput != GetStdHandle(STD_INPUT_HANDLE)) { CloseHandle(sinfo.hStdInput); } if (sinfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE)) { CloseHandle(sinfo.hStdOutput); } if (sinfo.hStdError != GetStdHandle(STD_ERROR_HANDLE)) { CloseHandle(sinfo.hStdError); } return ret; }
static w_query_expr *name_parser(w_query *query, json_t *term, bool caseless) { const char *pattern = NULL, *scope = "basename"; const char *which = caseless ? "iname" : "name"; struct name_data *data; json_t *name; w_ht_t *map = NULL; if (!json_is_array(term)) { ignore_result(asprintf(&query->errmsg, "Expected array for '%s' term", which)); return NULL; } if (json_array_size(term) > 3) { ignore_result(asprintf(&query->errmsg, "Invalid number of arguments for '%s' term", which)); return NULL; } if (json_array_size(term) == 3) { json_t *jscope; jscope = json_array_get(term, 2); if (!json_is_string(jscope)) { ignore_result(asprintf(&query->errmsg, "Argument 3 to '%s' must be a string", which)); return NULL; } scope = json_string_value(jscope); if (strcmp(scope, "basename") && strcmp(scope, "wholename")) { ignore_result(asprintf(&query->errmsg, "Invalid scope '%s' for %s expression", scope, which)); return NULL; } } name = json_array_get(term, 1); if (json_is_array(name)) { uint32_t i; for (i = 0; i < json_array_size(name); i++) { if (!json_is_string(json_array_get(name, i))) { ignore_result(asprintf(&query->errmsg, "Argument 2 to '%s' must be either a string or an array of string", which)); return NULL; } } map = w_ht_new(json_array_size(name), &w_ht_string_funcs); for (i = 0; i < json_array_size(name); i++) { w_string_t *element; const char *ele; ele = json_string_value(json_array_get(name, i)); if (caseless) { element = w_string_new_lower(ele); } else { element = w_string_new(ele); } w_ht_set(map, w_ht_ptr_val(element), 1); w_string_delref(element); } } else if (json_is_string(name)) { pattern = json_string_value(name); } else { ignore_result(asprintf(&query->errmsg, "Argument 2 to '%s' must be either a string or an array of string", which)); return NULL; } data = calloc(1, sizeof(*data)); if (pattern) { data->name = w_string_new(pattern); } data->map = map; data->caseless = caseless; data->wholename = !strcmp(scope, "wholename"); return w_query_expr_new(eval_name, dispose_name, data); }