Beispiel #1
0
/* subscribe /root subname {query}
 * Subscribes the client connection to the specified root. */
static void cmd_subscribe(
    struct watchman_client* clientbase,
    const json_ref& args) {
  std::shared_ptr<watchman_client_subscription> sub;
  json_ref resp, initial_subscription_results;
  json_ref jfield_list;
  json_ref jname;
  std::shared_ptr<w_query> query;
  json_ref query_spec;
  int defer = true; /* can't use bool because json_unpack requires int */
  json_ref defer_list;
  json_ref drop_list;
  struct watchman_user_client *client =
      (struct watchman_user_client *)clientbase;

  if (json_array_size(args) != 4) {
    send_error_response(client, "wrong number of arguments for subscribe");
    return;
  }

  auto root = resolve_root_or_err(client, args, 1, true);
  if (!root) {
    return;
  }

  jname = args.at(2);
  if (!json_is_string(jname)) {
    send_error_response(
        client, "expected 2nd parameter to be subscription name");
    return;
  }

  query_spec = args.at(3);

  query = w_query_parse(root, query_spec);

  defer_list = query_spec.get_default("defer");
  if (defer_list && !json_is_array(defer_list)) {
    send_error_response(client, "defer field must be an array of strings");
    return;
  }

  drop_list = query_spec.get_default("drop");
  if (drop_list && !json_is_array(drop_list)) {
    send_error_response(client, "drop field must be an array of strings");
    return;
  }

  sub = std::make_shared<watchman_client_subscription>(
      root, client->shared_from_this());

  sub->name = json_to_w_string(jname);
  sub->query = query;

  json_unpack(query_spec, "{s?:b}", "defer_vcs", &defer);
  sub->vcs_defer = defer;

  if (drop_list || defer_list) {
    size_t i;

    if (defer_list) {
      for (i = 0; i < json_array_size(defer_list); i++) {
        sub->drop_or_defer[json_to_w_string(json_array_get(defer_list, i))] =
            false;
      }
    }
    if (drop_list) {
      for (i = 0; i < json_array_size(drop_list); i++) {
        sub->drop_or_defer[json_to_w_string(json_array_get(drop_list, i))] =
            true;
      }
    }
  }

  // If they want SCM aware results we should wait for SCM events to finish
  // before dispatching subscriptions
  if (query->since_spec && query->since_spec->hasScmParams()) {
    sub->vcs_defer = true;

    // If they didn't specify any drop/defer behavior, default to a reasonable
    // setting that works together with the fsmonitor extension for hg.
    if (sub->drop_or_defer.find("hg.update") == sub->drop_or_defer.end()) {
      sub->drop_or_defer["hg.update"] = false; // defer
    }
  }

  // Connect the root to our subscription
  {
    std::weak_ptr<watchman_client> clientRef(client->shared_from_this());
    client->unilateralSub.insert(std::make_pair(
        sub,
        root->unilateralResponses->subscribe(
            [clientRef, sub]() {
              auto client = clientRef.lock();
              if (client) {
                client->ping->notify();
              }
            },
            sub->name)));
  }

  client->subscriptions[sub->name] = sub;

  resp = make_response();
  resp.set("subscribe", json_ref(jname));

  add_root_warnings_to_response(resp, root);
  ClockSpec position;
  initial_subscription_results = sub->buildSubscriptionResults(root, position);
  resp.set("clock", position.toJson());

  send_and_dispose_response(client, std::move(resp));
  if (initial_subscription_results) {
    send_and_dispose_response(client, std::move(initial_subscription_results));
  }
}
Beispiel #2
0
/* 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);
}
Beispiel #3
0
static void cmd_flush_subscriptions(
    struct watchman_client* clientbase,
    const json_ref& args) {
  auto client = (watchman_user_client*)clientbase;

  int sync_timeout;
  json_ref subs(nullptr);

  if (json_array_size(args) == 3) {
    auto& sync_timeout_obj = args.at(2).get("sync_timeout");
    subs = args.at(2).get_default("subscriptions", nullptr);
    if (!json_is_integer(sync_timeout_obj)) {
      send_error_response(client, "'sync_timeout' must be an integer");
      return;
    }
    sync_timeout = json_integer_value(sync_timeout_obj);
  } else {
    send_error_response(
        client, "wrong number of arguments to 'flush-subscriptions'");
    return;
  }

  auto root = resolve_root_or_err(client, args, 1, false);
  if (!root) {
    return;
  }

  std::vector<w_string> subs_to_sync;
  if (subs) {
    if (!json_is_array(subs)) {
      send_error_response(
          client,
          "expected 'subscriptions' to be an array of subscription names");
      return;
    }

    for (auto& sub_name : subs.array()) {
      if (!json_is_string(sub_name)) {
        send_error_response(
            client,
            "expected 'subscriptions' to be an array of subscription names");
        return;
      }

      auto& sub_name_str = json_to_w_string(sub_name);
      auto sub_iter = client->subscriptions.find(sub_name_str);
      if (sub_iter == client->subscriptions.end()) {
        send_error_response(
            client,
            "this client does not have a subscription named '%s'",
            sub_name_str.c_str());
        return;
      }
      auto& sub = sub_iter->second;
      if (sub->root != root) {
        send_error_response(
            client,
            "subscription '%s' is on root '%s' different from command root "
            "'%s'",
            sub_name_str.c_str(),
            sub->root->root_path.c_str(),
            root->root_path.c_str());
        return;
      }

      subs_to_sync.push_back(sub_name_str);
    }
  } else {
    // Look for all subscriptions matching this root.
    for (auto& sub_iter : client->subscriptions) {
      if (sub_iter.second->root == root) {
        subs_to_sync.push_back(sub_iter.first);
      }
    }
  }

  if (!root->syncToNow(std::chrono::milliseconds(sync_timeout))) {
    send_error_response(client, "sync_timeout expired");
    return;
  }

  auto resp = make_response();
  auto synced = json_array();
  auto no_sync_needed = json_array();
  auto dropped = json_array();

  for (auto& sub_name_str : subs_to_sync) {
    auto sub_iter = client->subscriptions.find(sub_name_str);
    auto& sub = sub_iter->second;

    sub_action action;
    w_string policy_name;
    std::tie(action, policy_name) = get_subscription_action(sub.get(), root);

    if (action == sub_action::drop) {
      auto position = root->view()->getMostRecentRootNumberAndTickValue();
      sub->last_sub_tick = position.ticks;
      sub->query->since_spec = watchman::make_unique<ClockSpec>(position);
      watchman::log(
          watchman::DBG,
          "(flush-subscriptions) dropping subscription notifications for ",
          sub->name,
          " until state ",
          policy_name,
          " is vacated. Advanced ticks to ",
          sub->last_sub_tick,
          "\n");
      json_array_append(dropped, w_string_to_json(sub_name_str));
    } else {
      // flush-subscriptions means that we _should NOT defer_ notifications. So
      // ignore defer and defer_vcs.
      ClockSpec out_position;
      watchman::log(
          watchman::DBG,
          "(flush-subscriptions) executing subscription ",
          sub->name,
          "\n");
      auto sub_result = sub->buildSubscriptionResults(root, out_position);
      if (sub_result) {
        send_and_dispose_response(client, std::move(sub_result));
        json_array_append(synced, w_string_to_json(sub_name_str));
      } else {
        json_array_append(no_sync_needed, w_string_to_json(sub_name_str));
      }
    }
  }

  resp.set({{"synced", std::move(synced)},
            {"no_sync_needed", std::move(no_sync_needed)},
            {"dropped", std::move(dropped)}});
  add_root_warnings_to_response(resp, root);
  send_and_dispose_response(client, std::move(resp));
}
Beispiel #4
0
/* since /root <timestamp> [patterns] */
static void cmd_since(struct watchman_client *client, json_t *args)
{
  const char *clockspec;
  w_root_t *root;
  w_query *query;
  char *errmsg = NULL;
  struct w_query_field_list field_list;
  w_query_res res;
  json_t *response, *clock_ele;
  json_t *file_list;
  char clockbuf[128];

  /* resolve the root */
  if (json_array_size(args) < 3) {
    send_error_response(client, "not enough arguments for 'since'");
    return;
  }

  root = resolve_root_or_err(client, args, 1, false);
  if (!root) {
    return;
  }

  clock_ele = json_array_get(args, 2);
  clockspec = json_string_value(clock_ele);
  if (!clockspec) {
    send_error_response(client,
        "expected argument 2 to be a valid clockspec");
    w_root_delref(root);
    return;
  }

  query = w_query_parse_legacy(args, &errmsg, 3, NULL, clockspec, NULL);
  if (errmsg) {
    send_error_response(client, "%s", errmsg);
    free(errmsg);
    w_root_delref(root);
    return;
  }

  w_query_legacy_field_list(&field_list);

  if (!w_query_execute(query, root, &res, NULL, NULL)) {
    send_error_response(client, "query failed: %s", res.errmsg);
    w_query_result_free(&res);
    w_root_delref(root);
    w_query_delref(query);
    return;
  }

  w_query_delref(query);

  file_list = w_query_results_to_json(&field_list,
                res.num_results, res.results);
  w_query_result_free(&res);

  response = make_response();
  if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) {
    set_prop(response, "clock", json_string_nocheck(clockbuf));
  }
  set_prop(response, "is_fresh_instance",
           json_pack("b", res.is_fresh_instance));
  set_prop(response, "files", file_list);

  send_and_dispose_response(client, response);
  w_root_delref(root);
}
Beispiel #5
0
/* query /root {query} */
static void cmd_query(struct watchman_client *client, json_t *args)
{
  w_root_t *root;
  w_query *query;
  json_t *query_spec;
  char *errmsg = NULL;
  w_query_res res;
  json_t *response;
  json_t *file_list, *jfield_list;
  char clockbuf[128];
  struct w_query_field_list field_list;

  if (json_array_size(args) != 3) {
    send_error_response(client, "wrong number of arguments for 'query'");
    return;
  }

  root = resolve_root_or_err(client, args, 1, false);
  if (!root) {
    return;
  }

  query_spec = json_array_get(args, 2);

  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);
    w_root_delref(root);
    return;
  }

  query = w_query_parse(root, query_spec, &errmsg);
  if (!query) {
    send_error_response(client, "failed to parse query: %s", errmsg);
    free(errmsg);
    w_root_delref(root);
    return;
  }

  if (client->client_mode) {
    query->sync_timeout = 0;
  }

  if (!w_query_execute(query, root, &res, NULL, NULL)) {
    send_error_response(client, "query failed: %s", res.errmsg);
    w_query_result_free(&res);
    w_root_delref(root);
    w_query_delref(query);
    return;
  }

  w_query_delref(query);

  file_list = w_query_results_to_json(&field_list,
                res.num_results, res.results);
  w_query_result_free(&res);

  response = make_response();
  if (clock_id_string(res.root_number, res.ticks, clockbuf, sizeof(clockbuf))) {
    set_prop(response, "clock", json_string_nocheck(clockbuf));
  }
  set_prop(response, "is_fresh_instance",
           json_pack("b", res.is_fresh_instance));
  set_prop(response, "files", file_list);
  add_root_warnings_to_response(response, root);

  send_and_dispose_response(client, response);
  w_root_delref(root);
}
Beispiel #6
0
/* 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;

  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;
  }

  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));
  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);
}
Beispiel #7
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 */
void cmd_trigger(struct watchman_client *client, json_t *args)
{
  struct watchman_rule *rules;
  w_root_t *root;
  uint32_t next_arg = 0;
  struct watchman_trigger_command *cmd;
  json_t *resp;
  const char *name;
  char buf[128];

  root = resolve_root_or_err(client, args, 1, true);
  if (!root) {
    return;
  }

  if (json_array_size(args) < 2) {
    send_error_response(client, "not enough arguments");
    goto done;
  }
  name = json_string_value(json_array_get(args, 2));
  if (!name) {
    send_error_response(client, "expected 2nd parameter to be trigger name");
    goto done;
  }

  if (!parse_watch_params(3, args, &rules, &next_arg, buf, sizeof(buf))) {
    send_error_response(client, "invalid rule spec: %s", buf);
    goto done;
  }

  if (next_arg >= json_array_size(args)) {
    send_error_response(client, "no command was specified");
    goto done;
  }

  cmd = calloc(1, sizeof(*cmd));
  if (!cmd) {
    send_error_response(client, "no memory!");
    goto done;
  }

  cmd->rules = rules;
  cmd->argc = json_array_size(args) - next_arg;
  cmd->argv = w_argv_copy_from_json(args, next_arg);
  if (!cmd->argv) {
    free(cmd);
    send_error_response(client, "unable to build argv array");
    goto done;
  }

  cmd->triggername = w_string_new(name);
  w_root_lock(root);
  w_ht_replace(root->commands, (w_ht_val_t)cmd->triggername, (w_ht_val_t)cmd);
  w_root_unlock(root);

  w_state_save();

  resp = make_response();
  set_prop(resp, "triggerid", json_string_nocheck(name));
  send_and_dispose_response(client, resp);
done:
  w_root_delref(root);
}
Beispiel #8
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", w_string_to_json(cmd->triggername));

    w_root_lock(root, "trigger-add");

    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_unicode_prop(resp, "disposition", "already_defined");
        w_trigger_command_free(cmd);
        cmd = NULL;
        need_save = false;
    } else {
        set_unicode_prop(resp, "disposition", 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);
}