/* 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); }
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; }
bool dispatch_command(struct watchman_client *client, json_t *args) { watchman_command_func func; const char *cmd_name; w_string_t *cmd; if (!json_array_size(args)) { send_error_response(client, "invalid command (expected an array with some elements!)"); return false; } cmd_name = json_string_value(json_array_get(args, 0)); if (!cmd_name) { send_error_response(client, "invalid command: expected element 0 to be the command name"); return false; } cmd = w_string_new(cmd_name); func = (watchman_command_func)w_ht_get(command_funcs, (w_ht_val_t)cmd); w_string_delref(cmd); if (func) { func(client, args); return true; } send_error_response(client, "unknown command %s", cmd_name); return false; }
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 w_root_t *lookup_running_pid(pid_t pid) { if (!running_kids) { return NULL; } return w_ht_val_ptr(w_ht_get(running_kids, pid)); }
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 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; }
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; }
/* 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); }
// may attempt to lock the root! bool w_parse_clockspec(w_root_t *root, json_t *value, struct w_clockspec_query *since, bool allow_cursor) { const char *str; int pid; if (json_is_integer(value)) { since->is_timestamp = true; since->tv.tv_usec = 0; since->tv.tv_sec = json_integer_value(value); return true; } str = json_string_value(value); if (!str) { return false; } if (allow_cursor && root && str[0] == 'n' && str[1] == ':') { w_string_t *name = w_string_new(str); since->is_timestamp = false; w_root_lock(root); // If we've never seen it before, ticks will be set to 0 // which is exactly what we want here. since->ticks = (uint32_t)w_ht_get(root->cursors, (w_ht_val_t)name); // 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_val_t)name, ++root->ticks); w_log(W_LOG_DBG, "resolved cursor %s -> %" PRIu32 "\n", str, since->ticks); w_string_delref(name); w_root_unlock(root); return true; } if (sscanf(str, "c:%d:%" PRIu32, &pid, &since->ticks) == 2) { since->is_timestamp = false; if (pid == getpid()) { if (root && since->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 */ w_root_lock(root); root->ticks++; w_root_unlock(root); } return true; } // If the pid doesn't match, they asked a different // incarnation of the server, so we treat them as having // never spoken to us before since->ticks = 0; return true; } return false; }
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 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"); } } }