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)); }
/* trigger /root triggername [watch patterns] -- cmd to run * Sets up a trigger so that we can execute a command when a change * is detected */ void cmd_trigger(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_trigger_command *cmd; json_t *resp; json_t *trig; char *errmsg = NULL; root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } if (json_array_size(args) < 3) { send_error_response(client, "not enough arguments"); goto done; } trig = json_array_get(args, 2); if (json_is_string(trig)) { trig = build_legacy_trigger(client, args); if (!trig) { goto done; } } else { // Add a ref so that we don't need to conditionally decref later // for the legacy case later json_incref(trig); } cmd = w_build_trigger_from_def(root, trig, &errmsg); json_decref(trig); if (!cmd) { send_error_response(client, "%s", errmsg); goto done; } w_root_lock(root); w_ht_replace(root->commands, w_ht_ptr_val(cmd->triggername), w_ht_ptr_val(cmd)); w_root_unlock(root); w_state_save(); resp = make_response(); set_prop(resp, "triggerid", json_string_nocheck(cmd->triggername->buf)); send_and_dispose_response(client, resp); done: if (errmsg) { free(errmsg); } w_root_delref(root); }
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; }
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_capability_supported(const char *name) { bool res; w_string_t *namestr = w_string_new(name); res = w_ht_get(capabilities, w_ht_ptr_val(namestr)); w_string_delref(namestr); return res; }
static bool suffix_generator( w_query *query, w_root_t *root, struct w_query_ctx *ctx, int64_t *num_walked) { uint32_t i; struct watchman_file *f; int64_t n = 0; bool result = true; for (i = 0; i < query->nsuffixes; i++) { // Head of suffix index for this suffix f = w_ht_val_ptr(w_ht_get(root->suffixes, w_ht_ptr_val(query->suffixes[i]))); // Walk and process for (; f; f = f->suffix_next) { ++n; if (!w_query_file_matches_relative_root(ctx, f)) { continue; } if (!w_query_process_file(query, ctx, f)) { result = false; goto done; } } } done: *num_walked = n; return result; }
// 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)); }
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)); }
/* parse an expression term. It can be one of: * "term" * ["term" <parameters>] */ w_query_expr *w_query_expr_parse(w_query *query, json_t *exp) { w_string_t *name; w_query_expr_parser parser; if (json_is_string(exp)) { name = w_string_new(json_string_value(exp)); } else if (json_is_array(exp) && json_array_size(exp) > 0) { json_t *first = json_array_get(exp, 0); if (!json_is_string(first)) { query->errmsg = strdup( "first element of an expression must be a string"); return NULL; } name = w_string_new(json_string_value(first)); } else { query->errmsg = strdup("expected array or string for an expression"); return NULL; } parser = w_ht_val_ptr(w_ht_get(term_hash, w_ht_ptr_val(name))); if (!parser) { ignore_result(asprintf(&query->errmsg, "unknown expression term '%s'", name->buf)); w_string_delref(name); return NULL; } w_string_delref(name); return parser(query, exp); }
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); }
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); }
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)); }
static struct watchman_dir_handle *inot_root_start_watch_dir( watchman_global_watcher_t watcher, w_root_t *root, struct watchman_dir *dir, struct timeval now, const char *path) { struct inot_root_state *state = root->watch; struct watchman_dir_handle *osdir = NULL; int newwd, err; unused_parameter(watcher); // Carry out our very strict opendir first to ensure that we're not // traversing symlinks in the context of this root osdir = w_dir_open(path); if (!osdir) { handle_open_errno(root, dir, now, "opendir", errno, NULL); return NULL; } // The directory might be different since the last time we looked at it, so // call inotify_add_watch unconditionally. newwd = inotify_add_watch(state->infd, path, WATCHMAN_INOTIFY_MASK); if (newwd == -1) { err = errno; if (errno == ENOSPC || errno == ENOMEM) { // Limits exceeded, no recovery from our perspective set_poison_state(root, dir->path, now, "inotify-add-watch", errno, inot_strerror(errno)); } else { handle_open_errno(root, dir, now, "inotify_add_watch", errno, inot_strerror(errno)); } w_dir_close(osdir); errno = err; return NULL; } // record mapping pthread_mutex_lock(&state->lock); w_ht_replace(state->wd_to_name, newwd, w_ht_ptr_val(dir->path)); pthread_mutex_unlock(&state->lock); w_log(W_LOG_DBG, "adding %d -> %s mapping\n", newwd, path); return osdir; }
/* trigger-del /root triggername * Delete a trigger from a root */ static void cmd_trigger_delete(struct watchman_client *client, json_t *args) { w_root_t *root; json_t *resp; json_t *jname; w_string_t *tname; bool res; root = resolve_root_or_err(client, args, 1, false); if (!root) { return; } if (json_array_size(args) != 3) { send_error_response(client, "wrong number of arguments"); w_root_delref(root); return; } jname = json_array_get(args, 2); if (!json_is_string(jname)) { send_error_response(client, "expected 2nd parameter to be trigger name"); w_root_delref(root); return; } tname = json_to_w_string_incref(jname); w_root_lock(root, "trigger-del"); res = w_ht_del(root->commands, w_ht_ptr_val(tname)); w_root_unlock(root); if (res) { w_state_save(); } w_string_delref(tname); resp = make_response(); set_prop(resp, "deleted", json_boolean(res)); json_incref(jname); set_prop(resp, "trigger", jname); send_and_dispose_response(client, resp); w_root_delref(root); }
/* unsubscribe /root subname * Cancels a subscription */ static void cmd_unsubscribe(struct watchman_client *clientbase, json_t *args) { w_root_t *root; const char *name; w_string_t *sname; bool deleted; json_t *resp; const json_t *jstr; struct watchman_user_client *client = (struct watchman_user_client *)clientbase; root = resolve_root_or_err(&client->client, args, 1, false); if (!root) { return; } jstr = json_array_get(args, 2); name = json_string_value(jstr); if (!name) { send_error_response(&client->client, "expected 2nd parameter to be subscription name"); w_root_delref(root); return; } sname = json_to_w_string_incref(jstr); pthread_mutex_lock(&w_client_lock); deleted = w_ht_del(client->subscriptions, w_ht_ptr_val(sname)); pthread_mutex_unlock(&w_client_lock); w_string_delref(sname); resp = make_response(); set_bytestring_prop(resp, "unsubscribe", name); set_prop(resp, "deleted", json_boolean(deleted)); send_and_dispose_response(&client->client, resp); w_root_delref(root); }
static struct watchman_command_handler_def *lookup( json_t *args, char **errmsg, int mode) { struct watchman_command_handler_def *def; const char *cmd_name; w_string_t *cmd; if (!json_array_size(args)) { ignore_result(asprintf(errmsg, "invalid command (expected an array with some elements!)")); return false; } cmd_name = json_string_value(json_array_get(args, 0)); if (!cmd_name) { ignore_result(asprintf(errmsg, "invalid command: expected element 0 to be the command name")); return false; } cmd = w_string_new(cmd_name); def = w_ht_val_ptr(w_ht_get(command_funcs, w_ht_ptr_val(cmd))); w_string_delref(cmd); if (def) { if (mode && ((def->flags & mode) == 0)) { ignore_result(asprintf(errmsg, "command %s not available in this mode", cmd_name)); return NULL; } return def; } if (mode) { ignore_result(asprintf(errmsg, "unknown command %s", cmd_name)); } return NULL; }
static bool eval_name(struct w_query_ctx *ctx, struct watchman_file *file, void *data) { struct name_data *name = data; w_string_t *str; if (name->wholename) { str = w_query_ctx_get_wholename(ctx); } else { str = file->name; } if (name->map) { bool matched; w_ht_val_t val; if (name->caseless) { str = w_string_dup_lower(str); if (!str) { return false; } } matched = w_ht_lookup(name->map, w_ht_ptr_val(str), &val, false); if (name->caseless) { w_string_delref(str); } return matched; } if (name->caseless) { return w_string_equal_caseless(str, name->name); } return w_string_equal(str, name->name); }
static bool suffix_generator( w_query *query, w_root_t *root, struct w_query_ctx *ctx) { uint32_t i; struct watchman_file *f; for (i = 0; i < query->nsuffixes; i++) { // Head of suffix index for this suffix f = w_ht_val_ptr(w_ht_get(root->suffixes, w_ht_ptr_val(query->suffixes[i]))); // Walk and process while (f) { if (!w_query_process_file(query, ctx, f)) { return false; } f = f->suffix_next; } } 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 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; }
// must be called with the root locked // spec can be null, in which case a fresh instance is assumed void w_clockspec_eval(w_root_t *root, const struct w_clockspec *spec, struct w_query_since *since) { if (spec == NULL) { since->is_timestamp = false; since->clock.is_fresh_instance = true; since->clock.ticks = 0; return; } if (spec->tag == w_cs_timestamp) { // just copy the values over since->is_timestamp = true; since->timestamp = spec->timestamp; return; } since->is_timestamp = false; if (spec->tag == w_cs_named_cursor) { w_ht_val_t ticks_val; w_string_t *cursor = spec->named_cursor.cursor; since->clock.is_fresh_instance = !w_ht_lookup(root->cursors, w_ht_ptr_val(cursor), &ticks_val, false); if (!since->clock.is_fresh_instance) { since->clock.is_fresh_instance = ticks_val < root->last_age_out_tick; } if (since->clock.is_fresh_instance) { since->clock.ticks = 0; } else { since->clock.ticks = (uint32_t)ticks_val; } // Bump the tick value and record it against the cursor. // We need to bump the tick value so that repeated queries // when nothing has changed in the filesystem won't continue // to return the same set of files; we only want the first // of these to return the files and the rest to return nothing // until something subsequently changes w_ht_replace(root->cursors, w_ht_ptr_val(cursor), ++root->ticks); w_log(W_LOG_DBG, "resolved cursor %.*s -> %" PRIu32 "\n", cursor->len, cursor->buf, since->clock.ticks); return; } // spec->tag == w_cs_clock if (spec->clock.start_time == proc_start_time && spec->clock.pid == proc_pid && spec->clock.root_number == root->number) { since->clock.is_fresh_instance = spec->clock.ticks < root->last_age_out_tick; if (since->clock.is_fresh_instance) { since->clock.ticks = 0; } else { since->clock.ticks = spec->clock.ticks; } if (spec->clock.ticks == root->ticks) { /* Force ticks to increment. This avoids returning and querying the * same tick value over and over when no files have changed in the * meantime */ root->ticks++; } return; } // If the pid, start time or root number don't match, they asked a different // incarnation of the server or a different instance of this root, so we treat // them as having never spoken to us before since->clock.is_fresh_instance = true; since->clock.ticks = 0; }
/* subscribe /root subname {query} * Subscribes the client connection to the specified root. */ void cmd_subscribe(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_client_subscription *sub; json_t *resp; const char *name; json_t *jfield_list; w_query *query; json_t *query_spec; struct w_query_field_list field_list; char *errmsg; if (json_array_size(args) != 4) { send_error_response(client, "wrong number of arguments for subscribe"); return; } root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be subscription name"); goto done; } query_spec = json_array_get(args, 3); jfield_list = json_object_get(query_spec, "fields"); if (!parse_field_list(jfield_list, &field_list, &errmsg)) { send_error_response(client, "invalid field list: %s", errmsg); free(errmsg); goto done; } query = w_query_parse(query_spec, &errmsg); if (!query) { send_error_response(client, "failed to parse query: %s", errmsg); free(errmsg); goto done; } sub = calloc(1, sizeof(*sub)); if (!sub) { send_error_response(client, "no memory!"); goto done; } sub->name = w_string_new(name); sub->query = query; memcpy(&sub->field_list, &field_list, sizeof(field_list)); sub->root = root; pthread_mutex_lock(&w_client_lock); w_ht_replace(client->subscriptions, w_ht_ptr_val(sub->name), w_ht_ptr_val(sub)); pthread_mutex_unlock(&w_client_lock); resp = make_response(); annotate_with_clock(root, resp); set_prop(resp, "subscribe", json_string(name)); send_and_dispose_response(client, resp); resp = build_subscription_results(sub, root); if (resp) { send_and_dispose_response(client, resp); } done: w_root_delref(root); }
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; }
static DIR *kqueue_root_start_watch_dir(watchman_global_watcher_t watcher, w_root_t *root, struct watchman_dir *dir, struct timeval now, const char *path) { struct kqueue_root_state *state = root->watch; DIR *osdir; struct stat st, osdirst; struct kevent k; int newwd; unused_parameter(watcher); osdir = opendir_nofollow(path); if (!osdir) { handle_open_errno(root, dir, now, "opendir", errno, NULL); return NULL; } newwd = open(path, O_NOFOLLOW|O_EVTONLY|O_CLOEXEC); if (newwd == -1) { // directory got deleted between opendir and open handle_open_errno(root, dir, now, "open", errno, NULL); closedir(osdir); return NULL; } if (fstat(newwd, &st) == -1 || fstat(dirfd(osdir), &osdirst) == -1) { // whaaa? w_log(W_LOG_ERR, "fstat on opened dir %s failed: %s\n", path, strerror(errno)); w_root_schedule_recrawl(root, "fstat failed"); close(newwd); closedir(osdir); return NULL; } if (st.st_dev != osdirst.st_dev || st.st_ino != osdirst.st_ino) { // directory got replaced between opendir and open -- at this point its // parent's being watched, so we let filesystem events take care of it handle_open_errno(root, dir, now, "open", ENOTDIR, NULL); close(newwd); closedir(osdir); return NULL; } memset(&k, 0, sizeof(k)); EV_SET(&k, newwd, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_WRITE|NOTE_DELETE|NOTE_EXTEND|NOTE_RENAME, 0, SET_DIR_BIT(dir->path)); // Our mapping needs to be visible before we add it to the queue, // otherwise we can get a wakeup and not know what it is pthread_mutex_lock(&state->lock); w_ht_replace(state->name_to_fd, w_ht_ptr_val(dir->path), newwd); w_ht_replace(state->fd_to_name, newwd, w_ht_ptr_val(dir->path)); pthread_mutex_unlock(&state->lock); if (kevent(state->kq_fd, &k, 1, NULL, 0, 0)) { w_log(W_LOG_DBG, "kevent EV_ADD dir %s failed: %s", path, strerror(errno)); close(newwd); pthread_mutex_lock(&state->lock); w_ht_del(state->name_to_fd, w_ht_ptr_val(dir->path)); w_ht_del(state->fd_to_name, newwd); pthread_mutex_unlock(&state->lock); } else { w_log(W_LOG_DBG, "kevent dir %s -> %d\n", dir->path->buf, newwd); } return osdir; }
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"); } } }
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; }
// The client thread reads and decodes json packets, // then dispatches the commands that it finds static void *client_thread(void *ptr) { struct watchman_client *client = ptr; struct watchman_event_poll pfd[2]; json_t *request; json_error_t jerr; w_stm_set_nonblock(client->stm, true); w_set_thread_name("client:stm=%p", client->stm); w_stm_get_events(client->stm, &pfd[0].evt); pfd[1].evt = client->ping; while (!stopping) { // Wait for input from either the client socket or // via the ping pipe, which signals that some other // thread wants to unilaterally send data to the client ignore_result(w_poll_events(pfd, 2, 2000)); if (stopping) { break; } if (pfd[0].ready) { request = w_json_buffer_next(&client->reader, client->stm, &jerr); if (!request && errno == EAGAIN) { // That's fine } else if (!request) { // Not so cool if (client->reader.wpos == client->reader.rpos) { // If they disconnected in between PDUs, no need to log // any error goto disconected; } send_error_response(client, "invalid json at position %d: %s", jerr.position, jerr.text); w_log(W_LOG_ERR, "invalid data from client: %s\n", jerr.text); goto disconected; } else if (request) { client->pdu_type = client->reader.pdu_type; dispatch_command(client, request, CMD_DAEMON); json_decref(request); } } if (pfd[1].ready) { w_event_test_and_clear(client->ping); } /* now send our response(s) */ while (client->head) { struct watchman_client_response *resp; /* de-queue the first response */ pthread_mutex_lock(&w_client_lock); resp = client->head; if (resp) { client->head = resp->next; if (client->tail == resp) { client->tail = NULL; } } pthread_mutex_unlock(&w_client_lock); if (resp) { bool ok; w_stm_set_nonblock(client->stm, false); /* Return the data in the same format that was used to ask for it */ ok = w_ser_write_pdu(client->pdu_type, &client->writer, client->stm, resp->json); json_decref(resp->json); free(resp); w_stm_set_nonblock(client->stm, true); if (!ok) { break; } } } } disconected: pthread_mutex_lock(&w_client_lock); w_ht_del(clients, w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); return NULL; }
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; }
/* trigger /root triggername [watch patterns] -- cmd to run * Sets up a trigger so that we can execute a command when a change * is detected */ static void cmd_trigger(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_trigger_command *cmd, *old; json_t *resp; json_t *trig; char *errmsg = NULL; bool need_save = true; root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } if (json_array_size(args) < 3) { send_error_response(client, "not enough arguments"); goto done; } trig = json_array_get(args, 2); if (json_is_string(trig)) { trig = build_legacy_trigger(root, client, args); if (!trig) { goto done; } } else { // Add a ref so that we don't need to conditionally decref later // for the legacy case later json_incref(trig); } cmd = w_build_trigger_from_def(root, trig, &errmsg); json_decref(trig); if (!cmd) { send_error_response(client, "%s", errmsg); goto done; } resp = make_response(); set_prop(resp, "triggerid", json_string_nocheck(cmd->triggername->buf)); w_root_lock(root); old = w_ht_val_ptr(w_ht_get(root->commands, w_ht_ptr_val(cmd->triggername))); if (old && json_equal(cmd->definition, old->definition)) { // Same definition: we don't and shouldn't touch things, so that we // preserve the associated trigger clock and don't cause the trigger // to re-run immediately set_prop(resp, "disposition", json_string_nocheck("already_defined")); w_trigger_command_free(cmd); cmd = NULL; need_save = false; } else { set_prop(resp, "disposition", json_string_nocheck( old ? "replaced" : "created")); w_ht_replace(root->commands, w_ht_ptr_val(cmd->triggername), w_ht_ptr_val(cmd)); // Force the trigger to be eligible to run now root->ticks++; root->pending_trigger_tick = root->ticks; } w_root_unlock(root); if (need_save) { w_state_save(); } send_and_dispose_response(client, resp); done: if (errmsg) { free(errmsg); } w_root_delref(root); }
/* subscribe /root subname {query} * Subscribes the client connection to the specified root. */ static void cmd_subscribe(struct watchman_client *client, json_t *args) { w_root_t *root; struct watchman_client_subscription *sub; json_t *resp; const char *name; json_t *jfield_list; w_query *query; json_t *query_spec; struct w_query_field_list field_list; char *errmsg; int defer = true; /* can't use bool because json_unpack requires int */ json_t *defer_list = NULL; json_t *drop_list = NULL; if (json_array_size(args) != 4) { send_error_response(client, "wrong number of arguments for subscribe"); return; } root = resolve_root_or_err(client, args, 1, true); if (!root) { return; } name = json_string_value(json_array_get(args, 2)); if (!name) { send_error_response(client, "expected 2nd parameter to be subscription name"); goto done; } query_spec = json_array_get(args, 3); jfield_list = json_object_get(query_spec, "fields"); if (!parse_field_list(jfield_list, &field_list, &errmsg)) { send_error_response(client, "invalid field list: %s", errmsg); free(errmsg); goto done; } query = w_query_parse(root, query_spec, &errmsg); if (!query) { send_error_response(client, "failed to parse query: %s", errmsg); free(errmsg); goto done; } json_unpack(query_spec, "{s?:o}", "defer", &defer_list); if (defer_list && !json_is_array(defer_list)) { send_error_response(client, "defer field must be an array of strings"); goto done; } json_unpack(query_spec, "{s?:o}", "drop", &drop_list); if (drop_list && !json_is_array(drop_list)) { send_error_response(client, "drop field must be an array of strings"); goto done; } sub = calloc(1, sizeof(*sub)); if (!sub) { send_error_response(client, "no memory!"); goto done; } sub->name = w_string_new(name); sub->query = query; json_unpack(query_spec, "{s?:b}", "defer_vcs", &defer); sub->vcs_defer = defer; if (drop_list || defer_list) { size_t i; sub->drop_or_defer = w_ht_new(2, &w_ht_string_funcs); if (defer_list) { for (i = 0; i < json_array_size(defer_list); i++) { w_ht_replace(sub->drop_or_defer, w_ht_ptr_val(w_string_new(json_string_value( json_array_get(defer_list, i)))), false); } } if (drop_list) { for (i = 0; i < json_array_size(drop_list); i++) { w_ht_replace(sub->drop_or_defer, w_ht_ptr_val(w_string_new(json_string_value( json_array_get(drop_list, i)))), true); } } } memcpy(&sub->field_list, &field_list, sizeof(field_list)); sub->root = root; pthread_mutex_lock(&w_client_lock); w_ht_replace(client->subscriptions, w_ht_ptr_val(sub->name), w_ht_ptr_val(sub)); pthread_mutex_unlock(&w_client_lock); resp = make_response(); annotate_with_clock(root, resp); set_prop(resp, "subscribe", json_string(name)); add_root_warnings_to_response(resp, root); send_and_dispose_response(client, resp); resp = build_subscription_results(sub, root); if (resp) { send_and_dispose_response(client, resp); } done: w_root_delref(root); }