+ if (!channel_key){
+ /* No channel found; normal exit. */
+ DBG("[notification-thread] No known channel associated with newly subscribed-to condition");
+ ret = 0;
+ goto end;
+ }
+
+ /* Fetch channel info for the matching channel. */
+ cds_lfht_lookup(state->channels_ht,
+ hash_channel_key(channel_key),
+ match_channel_info,
+ channel_key,
+ &iter);
+ node = cds_lfht_iter_get_node(&iter);
+ assert(node);
+ channel_info = caa_container_of(node, struct channel_info,
+ channels_ht_node);
+
+ /* Retrieve the channel's last sample, if it exists. */
+ cds_lfht_lookup(state->channel_state_ht,
+ hash_channel_key(channel_key),
+ match_channel_state_sample,
+ channel_key,
+ &iter);
+ node = cds_lfht_iter_get_node(&iter);
+ if (node) {
+ last_sample = caa_container_of(node,
+ struct channel_state_sample,
+ channel_state_ht_node);
+ } else {
+ /* Nothing to evaluate, no sample was ever taken. Normal exit */
+ DBG("[notification-thread] No channel sample associated with newly subscribed-to condition");
+ ret = 0;
+ goto end;
+ }
+
+ ret = evaluate_buffer_condition(condition, evaluation, state,
+ NULL, last_sample,
+ 0, channel_info->session_info->consumed_data_size,
+ channel_info);
+ if (ret) {
+ WARN("[notification-thread] Fatal error occurred while evaluating a newly subscribed-to condition");
+ goto end;
+ }
+
+ *session_uid = channel_info->session_info->uid;
+ *session_gid = channel_info->session_info->gid;
+end:
+ return ret;
+}
+
+static
+const char *get_condition_session_name(const struct lttng_condition *condition)
+{
+ const char *session_name = NULL;
+ enum lttng_condition_status status;
+
+ switch (lttng_condition_get_type(condition)) {
+ case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW:
+ case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH:
+ status = lttng_condition_buffer_usage_get_session_name(
+ condition, &session_name);
+ break;
+ case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE:
+ status = lttng_condition_session_consumed_size_get_session_name(
+ condition, &session_name);
+ break;
+ case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING:
+ case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED:
+ status = lttng_condition_session_rotation_get_session_name(
+ condition, &session_name);
+ break;
+ default:
+ abort();
+ }
+ if (status != LTTNG_CONDITION_STATUS_OK) {
+ ERR("[notification-thread] Failed to retrieve session rotation condition's session name");
+ goto end;
+ }
+end:
+ return session_name;
+}
+
+/* This function must be called with the RCU read lock held. */
+static
+int evaluate_session_condition_for_client(
+ const struct lttng_condition *condition,
+ struct notification_thread_state *state,
+ struct lttng_evaluation **evaluation,
+ uid_t *session_uid, gid_t *session_gid)
+{
+ int ret;
+ struct cds_lfht_iter iter;
+ struct cds_lfht_node *node;
+ const char *session_name;
+ struct session_info *session_info = NULL;
+
+ session_name = get_condition_session_name(condition);
+
+ /* Find the session associated with the trigger. */
+ cds_lfht_lookup(state->sessions_ht,
+ hash_key_str(session_name, lttng_ht_seed),
+ match_session,
+ session_name,
+ &iter);
+ node = cds_lfht_iter_get_node(&iter);
+ if (!node) {
+ DBG("[notification-thread] No known session matching name \"%s\"",
+ session_name);
+ ret = 0;
+ goto end;
+ }
+
+ session_info = caa_container_of(node, struct session_info,
+ sessions_ht_node);
+ session_info_get(session_info);
+
+ /*
+ * Evaluation is performed in-line here since only one type of
+ * session-bound condition is handled for the moment.
+ */
+ switch (lttng_condition_get_type(condition)) {
+ case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING:
+ if (!session_info->rotation.ongoing) {
+ ret = 0;
+ goto end_session_put;
+ }
+
+ *evaluation = lttng_evaluation_session_rotation_ongoing_create(
+ session_info->rotation.id);
+ if (!*evaluation) {
+ /* Fatal error. */
+ ERR("[notification-thread] Failed to create session rotation ongoing evaluation for session \"%s\"",
+ session_info->name);
+ ret = -1;
+ goto end_session_put;
+ }
+ ret = 0;
+ break;
+ default:
+ ret = 0;
+ goto end_session_put;
+ }
+
+ *session_uid = session_info->uid;
+ *session_gid = session_info->gid;
+
+end_session_put:
+ session_info_put(session_info);
+end:
+ return ret;
+}
+
+/* This function must be called with the RCU read lock held. */
+static
+int evaluate_condition_for_client(const struct lttng_trigger *trigger,
+ const struct lttng_condition *condition,
+ struct notification_client *client,
+ struct notification_thread_state *state)
+{
+ int ret;
+ struct lttng_evaluation *evaluation = NULL;
+ struct notification_client_list client_list = { 0 };
+ struct notification_client_list_element client_list_element = { 0 };
+ uid_t object_uid = 0;
+ gid_t object_gid = 0;
+
+ assert(trigger);
+ assert(condition);
+ assert(client);
+ assert(state);
+
+ switch (get_condition_binding_object(condition)) {
+ case LTTNG_OBJECT_TYPE_SESSION:
+ ret = evaluate_session_condition_for_client(condition, state,
+ &evaluation, &object_uid, &object_gid);
+ break;
+ case LTTNG_OBJECT_TYPE_CHANNEL:
+ ret = evaluate_channel_condition_for_client(condition, state,
+ &evaluation, &object_uid, &object_gid);
+ break;
+ case LTTNG_OBJECT_TYPE_NONE:
+ ret = 0;
+ goto end;
+ case LTTNG_OBJECT_TYPE_UNKNOWN:
+ default:
+ ret = -1;
+ goto end;
+ }
+
+ if (!evaluation) {
+ /* Evaluation yielded nothing. Normal exit. */
+ DBG("[notification-thread] Newly subscribed-to condition evaluated to false, nothing to report to client");
+ ret = 0;
+ goto end;
+ }
+
+ /*
+ * Create a temporary client list with the client currently
+ * subscribing.
+ */
+ cds_lfht_node_init(&client_list.notification_trigger_ht_node);
+ CDS_INIT_LIST_HEAD(&client_list.list);
+ client_list.trigger = trigger;
+
+ CDS_INIT_LIST_HEAD(&client_list_element.node);
+ client_list_element.client = client;
+ cds_list_add(&client_list_element.node, &client_list.list);
+
+ /* Send evaluation result to the newly-subscribed client. */
+ DBG("[notification-thread] Newly subscribed-to condition evaluated to true, notifying client");
+ ret = send_evaluation_to_clients(trigger, evaluation, &client_list,
+ state, object_uid, object_gid);
+
+end:
+ return ret;
+}
+
+static
+int notification_thread_client_subscribe(struct notification_client *client,
+ struct lttng_condition *condition,
+ struct notification_thread_state *state,
+ enum lttng_notification_channel_status *_status)
+{
+ int ret = 0;
+ struct notification_client_list *client_list;
+ struct lttng_condition_list_element *condition_list_element = NULL;
+ struct notification_client_list_element *client_list_element = NULL;
+ enum lttng_notification_channel_status status =
+ LTTNG_NOTIFICATION_CHANNEL_STATUS_OK;
+
+ /*
+ * Ensure that the client has not already subscribed to this condition
+ * before.
+ */
+ cds_list_for_each_entry(condition_list_element, &client->condition_list, node) {
+ if (lttng_condition_is_equal(condition_list_element->condition,
+ condition)) {
+ status = LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED;
+ goto end;
+ }
+ }
+
+ condition_list_element = zmalloc(sizeof(*condition_list_element));
+ if (!condition_list_element) {
+ ret = -1;
+ goto error;
+ }
+ client_list_element = zmalloc(sizeof(*client_list_element));
+ if (!client_list_element) {
+ ret = -1;
+ goto error;
+ }
+
+ rcu_read_lock();
+
+ /*
+ * Add the newly-subscribed condition to the client's subscription list.
+ */
+ CDS_INIT_LIST_HEAD(&condition_list_element->node);
+ condition_list_element->condition = condition;
+ cds_list_add(&condition_list_element->node, &client->condition_list);
+
+ client_list = get_client_list_from_condition(state, condition);
+ if (!client_list) {
+ /*
+ * No notification-emiting trigger registered with this
+ * condition. We don't evaluate the condition right away
+ * since this trigger is not registered yet.
+ */
+ free(client_list_element);
+ goto end_unlock;
+ }
+
+ /*
+ * The condition to which the client just subscribed is evaluated
+ * at this point so that conditions that are already TRUE result
+ * in a notification being sent out.
+ */
+ if (evaluate_condition_for_client(client_list->trigger, condition,
+ client, state)) {
+ WARN("[notification-thread] Evaluation of a condition on client subscription failed, aborting.");
+ ret = -1;
+ free(client_list_element);
+ goto end_unlock;
+ }
+
+ /*
+ * Add the client to the list of clients interested in a given trigger
+ * if a "notification" trigger with a corresponding condition was
+ * added prior.
+ */
+ client_list_element->client = client;
+ CDS_INIT_LIST_HEAD(&client_list_element->node);
+ cds_list_add(&client_list_element->node, &client_list->list);
+end_unlock:
+ rcu_read_unlock();
+end:
+ if (_status) {
+ *_status = status;
+ }
+ return ret;
+error:
+ free(condition_list_element);
+ free(client_list_element);
+ return ret;
+}
+
+static
+int notification_thread_client_unsubscribe(
+ struct notification_client *client,
+ struct lttng_condition *condition,
+ struct notification_thread_state *state,
+ enum lttng_notification_channel_status *_status)
+{
+ struct notification_client_list *client_list;
+ struct lttng_condition_list_element *condition_list_element,
+ *condition_tmp;
+ struct notification_client_list_element *client_list_element,
+ *client_tmp;
+ bool condition_found = false;
+ enum lttng_notification_channel_status status =
+ LTTNG_NOTIFICATION_CHANNEL_STATUS_OK;
+
+ /* Remove the condition from the client's condition list. */
+ cds_list_for_each_entry_safe(condition_list_element, condition_tmp,
+ &client->condition_list, node) {
+ if (!lttng_condition_is_equal(condition_list_element->condition,
+ condition)) {
+ continue;
+ }
+
+ cds_list_del(&condition_list_element->node);
+ /*
+ * The caller may be iterating on the client's conditions to
+ * tear down a client's connection. In this case, the condition
+ * will be destroyed at the end.
+ */
+ if (condition != condition_list_element->condition) {
+ lttng_condition_destroy(
+ condition_list_element->condition);
+ }
+ free(condition_list_element);
+ condition_found = true;
+ break;
+ }
+
+ if (!condition_found) {