X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Fbin%2Flttng-sessiond%2Fnotification-thread-events.c;h=40450304297230b96cdf980afde1b516dc6c02c9;hp=f84a08d38f1d20c94c62d47d9391ecd7aa2e217e;hb=f8522f5cc357c0d80257d5ace1cd556a88305c0c;hpb=0ca52944daa81aea35eb372ccdb118c42a002d13 diff --git a/src/bin/lttng-sessiond/notification-thread-events.c b/src/bin/lttng-sessiond/notification-thread-events.c index f84a08d38..404503042 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ b/src/bin/lttng-sessiond/notification-thread-events.c @@ -1,20 +1,12 @@ /* - * Copyright (C) 2017 - Jérémie Galarneau + * Copyright (C) 2017 Jérémie Galarneau * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License, version 2 only, as - * published by the Free Software Foundation. + * SPDX-License-Identifier: GPL-2.0-only * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "lttng/action/action.h" +#include "lttng/trigger/trigger-internal.h" #define _LGPL_SOURCE #include #include @@ -35,6 +27,7 @@ #include #include #include +#include #include #include @@ -60,7 +53,7 @@ enum lttng_object_type { struct lttng_trigger_list_element { /* No ownership of the trigger object is assumed. */ - const struct lttng_trigger *trigger; + struct lttng_trigger *trigger; struct cds_list_head node; }; @@ -70,6 +63,8 @@ struct lttng_channel_trigger_list { struct cds_list_head list; /* Node in the channel_triggers_ht */ struct cds_lfht_node channel_triggers_ht_node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; }; /* @@ -116,6 +111,9 @@ struct lttng_session_trigger_list { struct lttng_trigger_ht_element { struct lttng_trigger *trigger; struct cds_lfht_node node; + struct cds_lfht_node node_by_name_uid; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; }; struct lttng_condition_list_element { @@ -123,93 +121,18 @@ struct lttng_condition_list_element { struct cds_list_head node; }; -struct notification_client_list_element { - struct notification_client *client; - struct cds_list_head node; -}; - -struct notification_client_list { - struct lttng_trigger *trigger; - struct cds_list_head list; - struct cds_lfht_node notification_trigger_ht_node; -}; - -struct notification_client { - int socket; - /* Client protocol version. */ - uint8_t major, minor; - uid_t uid; - gid_t gid; - /* - * Indicates if the credentials and versions of the client have been - * checked. - */ - bool validated; - /* - * Conditions to which the client's notification channel is subscribed. - * List of struct lttng_condition_list_node. The condition member is - * owned by the client. - */ - struct cds_list_head condition_list; - struct cds_lfht_node client_socket_ht_node; - struct { - struct { - /* - * During the reception of a message, the reception - * buffers' "size" is set to contain the current - * message's complete payload. - */ - struct lttng_dynamic_buffer buffer; - /* Bytes left to receive for the current message. */ - size_t bytes_to_receive; - /* Type of the message being received. */ - enum lttng_notification_channel_message_type msg_type; - /* - * Indicates whether or not credentials are expected - * from the client. - */ - bool expect_creds; - /* - * Indicates whether or not credentials were received - * from the client. - */ - bool creds_received; - /* Only used during credentials reception. */ - lttng_sock_cred creds; - } inbound; - struct { - /* - * Indicates whether or not a notification addressed to - * this client was dropped because a command reply was - * already buffered. - * - * A notification is dropped whenever the buffer is not - * empty. - */ - bool dropped_notification; - /* - * Indicates whether or not a command reply is already - * buffered. In this case, it means that the client is - * not consuming command replies before emitting a new - * one. This could be caused by a protocol error or a - * misbehaving/malicious client. - */ - bool queued_command_reply; - struct lttng_dynamic_buffer buffer; - } outbound; - } communication; -}; - struct channel_state_sample { struct channel_key key; struct cds_lfht_node channel_state_ht_node; uint64_t highest_usage; uint64_t lowest_usage; uint64_t channel_total_consumed; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; }; static unsigned long hash_channel_key(struct channel_key *key); -static int evaluate_condition(const struct lttng_condition *condition, +static int evaluate_buffer_condition(const struct lttng_condition *condition, struct lttng_evaluation **evaluation, const struct notification_thread_state *state, const struct channel_state_sample *previous_sample, @@ -258,20 +181,37 @@ void lttng_session_trigger_list_destroy( struct lttng_session_trigger_list *list); static int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, - const struct lttng_trigger *trigger); + struct lttng_trigger *trigger); + +static +int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status transmission_status, + struct notification_thread_state *state); +static +void free_lttng_trigger_ht_element_rcu(struct rcu_head *node); static -int match_client(struct cds_lfht_node *node, const void *key) +int match_client_socket(struct cds_lfht_node *node, const void *key) { /* This double-cast is intended to supress pointer-to-cast warning. */ - int socket = (int) (intptr_t) key; - struct notification_client *client; + const int socket = (int) (intptr_t) key; + const struct notification_client *client = caa_container_of(node, + struct notification_client, client_socket_ht_node); - client = caa_container_of(node, struct notification_client, - client_socket_ht_node); + return client->socket == socket; +} + +static +int match_client_id(struct cds_lfht_node *node, const void *key) +{ + /* This double-cast is intended to supress pointer-to-cast warning. */ + const notification_client_id id = *((notification_client_id *) key); + const struct notification_client *client = caa_container_of( + node, struct notification_client, client_id_ht_node); - return !!(client->socket == socket); + return client->id == id; } static @@ -326,52 +266,106 @@ int match_channel_info(struct cds_lfht_node *node, const void *key) } static -int match_condition(struct cds_lfht_node *node, const void *key) +int match_trigger(struct cds_lfht_node *node, const void *key) { - struct lttng_condition *condition_key = (struct lttng_condition *) key; - struct lttng_trigger_ht_element *trigger; - struct lttng_condition *condition; + struct lttng_trigger *trigger_key = (struct lttng_trigger *) key; + struct lttng_trigger_ht_element *trigger_ht_element; - trigger = caa_container_of(node, struct lttng_trigger_ht_element, + trigger_ht_element = caa_container_of(node, struct lttng_trigger_ht_element, node); - condition = lttng_trigger_get_condition(trigger->trigger); - assert(condition); - return !!lttng_condition_is_equal(condition_key, condition); + return !!lttng_trigger_is_equal(trigger_key, trigger_ht_element->trigger); } static -int match_client_list(struct cds_lfht_node *node, const void *key) +int match_client_list_condition(struct cds_lfht_node *node, const void *key) { - struct lttng_trigger *trigger_key = (struct lttng_trigger *) key; + struct lttng_condition *condition_key = (struct lttng_condition *) key; struct notification_client_list *client_list; - struct lttng_condition *condition; - struct lttng_condition *condition_key = lttng_trigger_get_condition( - trigger_key); + const struct lttng_condition *condition; assert(condition_key); client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); - condition = lttng_trigger_get_condition(client_list->trigger); + notification_trigger_clients_ht_node); + condition = lttng_trigger_get_const_condition(client_list->trigger); return !!lttng_condition_is_equal(condition_key, condition); } static -int match_client_list_condition(struct cds_lfht_node *node, const void *key) +int match_session(struct cds_lfht_node *node, const void *key) { - struct lttng_condition *condition_key = (struct lttng_condition *) key; - struct notification_client_list *client_list; - struct lttng_condition *condition; + const char *name = key; + struct session_info *session_info = caa_container_of( + node, struct session_info, sessions_ht_node); - assert(condition_key); + return !strcmp(session_info->name, name); +} - client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); - condition = lttng_trigger_get_condition(client_list->trigger); +/* + * Match trigger based on name and credentials only. + * Name duplication is NOT allowed for the same uid. + */ +static +int match_trigger_by_name_uid(struct cds_lfht_node *node, + const void *key) +{ + bool match = false; + const char *name; + const char *key_name; + enum lttng_trigger_status status; + const struct lttng_credentials *key_creds; + const struct lttng_credentials *node_creds; + const struct lttng_trigger *trigger_key = + (const struct lttng_trigger *) key; + const struct lttng_trigger_ht_element *trigger_ht_element = + caa_container_of(node, + struct lttng_trigger_ht_element, + node_by_name_uid); + + status = lttng_trigger_get_name(trigger_ht_element->trigger, &name); + assert(status == LTTNG_TRIGGER_STATUS_OK); + + status = lttng_trigger_get_name(trigger_key, &key_name); + assert(status == LTTNG_TRIGGER_STATUS_OK); + + /* Compare the names. */ + if (strcmp(name, key_name) != 0) { + goto end; + } - return !!lttng_condition_is_equal(condition_key, condition); + /* Compare the owners' UIDs. */ + key_creds = lttng_trigger_get_credentials(trigger_key); + node_creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); + + match = lttng_credentials_is_equal_uid(key_creds, node_creds); + +end: + return match; +} + +/* + * Hash trigger based on name and credentials only. + */ +static +unsigned long hash_trigger_by_name_uid(const struct lttng_trigger *trigger) +{ + unsigned long hash = 0; + const struct lttng_credentials *trigger_creds; + const char *trigger_name; + enum lttng_trigger_status status; + + status = lttng_trigger_get_name(trigger, &trigger_name); + if (status == LTTNG_TRIGGER_STATUS_OK) { + hash = hash_key_str(trigger_name, lttng_ht_seed); + } + + trigger_creds = lttng_trigger_get_credentials(trigger); + hash ^= hash_key_ulong((void *) (unsigned long) LTTNG_OPTIONAL_GET(trigger_creds->uid), + lttng_ht_seed); + + return hash; } static @@ -483,6 +477,49 @@ unsigned long hash_channel_key(struct channel_key *key) return key_hash ^ domain_hash; } +static +unsigned long hash_client_socket(int socket) +{ + return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed); +} + +static +unsigned long hash_client_id(notification_client_id id) +{ + return hash_key_u64(&id, lttng_ht_seed); +} + +/* + * Get the type of object to which a given condition applies. Bindings let + * the notification system evaluate a trigger's condition when a given + * object's state is updated. + * + * For instance, a condition bound to a channel will be evaluated everytime + * the channel's state is changed by a channel monitoring sample. + */ +static +enum lttng_object_type get_condition_binding_object( + const struct lttng_condition *condition) +{ + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + return LTTNG_OBJECT_TYPE_CHANNEL; + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + return LTTNG_OBJECT_TYPE_SESSION; + default: + return LTTNG_OBJECT_TYPE_UNKNOWN; + } +} + +static +void free_channel_info_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct channel_info, rcu_node)); +} + static void channel_info_destroy(struct channel_info *channel_info) { @@ -498,7 +535,13 @@ void channel_info_destroy(struct channel_info *channel_info) if (channel_info->name) { free(channel_info->name); } - free(channel_info); + call_rcu(&channel_info->rcu_node, free_channel_info_rcu); +} + +static +void free_session_info_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct session_info, rcu_node)); } /* Don't call directly, use the ref-counting mechanism. */ @@ -522,7 +565,7 @@ void session_info_destroy(void *_data) &session_info->sessions_ht_node); rcu_read_unlock(); free(session_info->name); - free(session_info); + call_rcu(&session_info->rcu_node, free_session_info_rcu); } static @@ -637,12 +680,123 @@ error: return NULL; } -/* This function must be called with the RCU read lock held. */ +LTTNG_HIDDEN +bool notification_client_list_get(struct notification_client_list *list) +{ + return urcu_ref_get_unless_zero(&list->ref); +} + static -int evaluate_condition_for_client(struct lttng_trigger *trigger, - struct lttng_condition *condition, - struct notification_client *client, - struct notification_thread_state *state) +void free_notification_client_list_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct notification_client_list, + rcu_node)); +} + +static +void notification_client_list_release(struct urcu_ref *list_ref) +{ + struct notification_client_list *list = + container_of(list_ref, typeof(*list), ref); + struct notification_client_list_element *client_list_element, *tmp; + + if (list->notification_trigger_clients_ht) { + rcu_read_lock(); + cds_lfht_del(list->notification_trigger_clients_ht, + &list->notification_trigger_clients_ht_node); + rcu_read_unlock(); + list->notification_trigger_clients_ht = NULL; + } + cds_list_for_each_entry_safe(client_list_element, tmp, + &list->list, node) { + free(client_list_element); + } + pthread_mutex_destroy(&list->lock); + call_rcu(&list->rcu_node, free_notification_client_list_rcu); +} + +static +struct notification_client_list *notification_client_list_create( + const struct lttng_trigger *trigger) +{ + struct notification_client_list *client_list = + zmalloc(sizeof(*client_list)); + + if (!client_list) { + goto error; + } + pthread_mutex_init(&client_list->lock, NULL); + urcu_ref_init(&client_list->ref); + cds_lfht_node_init(&client_list->notification_trigger_clients_ht_node); + CDS_INIT_LIST_HEAD(&client_list->list); + client_list->trigger = trigger; +error: + return client_list; +} + +static +void publish_notification_client_list( + struct notification_thread_state *state, + struct notification_client_list *list) +{ + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(list->trigger); + + assert(!list->notification_trigger_clients_ht); + notification_client_list_get(list); + + list->notification_trigger_clients_ht = + state->notification_trigger_clients_ht; + + rcu_read_lock(); + cds_lfht_add(state->notification_trigger_clients_ht, + lttng_condition_hash(condition), + &list->notification_trigger_clients_ht_node); + rcu_read_unlock(); +} + +LTTNG_HIDDEN +void notification_client_list_put(struct notification_client_list *list) +{ + if (!list) { + return; + } + return urcu_ref_put(&list->ref, notification_client_list_release); +} + +/* Provides a reference to the returned list. */ +static +struct notification_client_list *get_client_list_from_condition( + struct notification_thread_state *state, + const struct lttng_condition *condition) +{ + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct notification_client_list *list = NULL; + + rcu_read_lock(); + cds_lfht_lookup(state->notification_trigger_clients_ht, + lttng_condition_hash(condition), + match_client_list_condition, + condition, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + list = container_of(node, struct notification_client_list, + notification_trigger_clients_ht_node); + list = notification_client_list_get(list) ? list : NULL; + } + + rcu_read_unlock(); + return list; +} + +static +int evaluate_channel_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; @@ -651,23 +805,17 @@ int evaluate_condition_for_client(struct lttng_trigger *trigger, struct channel_key *channel_key = NULL; struct channel_state_sample *last_sample = NULL; struct lttng_channel_trigger_list *channel_trigger_list = NULL; - struct lttng_evaluation *evaluation = NULL; - struct notification_client_list client_list = { 0 }; - struct notification_client_list_element client_list_element = { 0 }; - assert(trigger); - assert(condition); - assert(client); - assert(state); + rcu_read_lock(); - /* Find the channel associated with the trigger. */ + /* Find the channel associated with the condition. */ cds_lfht_for_each_entry(state->channel_triggers_ht, &iter, - channel_trigger_list , channel_triggers_ht_node) { + channel_trigger_list, channel_triggers_ht_node) { struct lttng_trigger_list_element *element; cds_list_for_each_entry(element, &channel_trigger_list->list, node) { const struct lttng_condition *current_condition = - lttng_trigger_get_const_condition( + lttng_trigger_get_const_condition( element->trigger); assert(current_condition); @@ -688,7 +836,7 @@ int evaluate_condition_for_client(struct lttng_trigger *trigger, if (!channel_key){ /* No channel found; normal exit. */ - DBG("[notification-thread] No channel associated with newly subscribed-to condition"); + DBG("[notification-thread] No known channel associated with newly subscribed-to condition"); ret = 0; goto end; } @@ -722,7 +870,7 @@ int evaluate_condition_for_client(struct lttng_trigger *trigger, goto end; } - ret = evaluate_condition(condition, &evaluation, state, + ret = evaluate_buffer_condition(condition, evaluation, state, NULL, last_sample, 0, channel_info->session_info->consumed_data_size, channel_info); @@ -731,64 +879,212 @@ int evaluate_condition_for_client(struct lttng_trigger *trigger, 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, channel_info->session_info->uid, - channel_info->session_info->gid); - + *session_uid = channel_info->session_info->uid; + *session_gid = channel_info->session_info->gid; end: + rcu_read_unlock(); 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) +const char *get_condition_session_name(const struct lttng_condition *condition) { - int ret = 0; - struct cds_lfht_iter iter; - struct cds_lfht_node *node; - 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; - } - } + const char *session_name = NULL; + enum lttng_condition_status status; - condition_list_element = zmalloc(sizeof(*condition_list_element)); - if (!condition_list_element) { + 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; +} + +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; + + rcu_read_lock(); + 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: + rcu_read_unlock(); + return ret; +} + +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 = { + .lock = PTHREAD_MUTEX_INITIALIZER, + }; + 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 (ret) { + /* Fatal error. */ + 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_clients_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 = NULL; + 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; } @@ -798,8 +1094,6 @@ int notification_thread_client_subscribe(struct notification_client *client, goto error; } - rcu_read_lock(); - /* * Add the newly-subscribed condition to the client's subscription list. */ @@ -807,35 +1101,32 @@ int notification_thread_client_subscribe(struct notification_client *client, condition_list_element->condition = condition; cds_list_add(&condition_list_element->node, &client->condition_list); - cds_lfht_lookup(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - match_client_list_condition, - condition, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { + 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; + goto end; } - client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); /* * 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. + * + * The client_list's trigger is used without locking the list itself. + * This is correct since the list doesn't own the trigger and the + * object is immutable. */ 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; + goto end; } /* @@ -845,13 +1136,17 @@ int notification_thread_client_subscribe(struct notification_client *client, */ client_list_element->client = client; CDS_INIT_LIST_HEAD(&client_list_element->node); + + pthread_mutex_lock(&client_list->lock); cds_list_add(&client_list_element->node, &client_list->list); -end_unlock: - rcu_read_unlock(); + pthread_mutex_unlock(&client_list->lock); end: if (_status) { *_status = status; } + if (client_list) { + notification_client_list_put(client_list); + } return ret; error: free(condition_list_element); @@ -866,8 +1161,6 @@ int notification_thread_client_unsubscribe( struct notification_thread_state *state, enum lttng_notification_channel_status *_status) { - struct cds_lfht_iter iter; - struct cds_lfht_node *node; struct notification_client_list *client_list; struct lttng_condition_list_element *condition_list_element, *condition_tmp; @@ -909,30 +1202,24 @@ int notification_thread_client_unsubscribe( * Remove the client from the list of clients interested the trigger * matching the condition. */ - rcu_read_lock(); - cds_lfht_lookup(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - match_client_list_condition, - condition, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - goto end_unlock; + client_list = get_client_list_from_condition(state, condition); + if (!client_list) { + goto end; } - client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); + pthread_mutex_lock(&client_list->lock); cds_list_for_each_entry_safe(client_list_element, client_tmp, &client_list->list, node) { - if (client_list_element->client->socket != client->socket) { + if (client_list_element->client->id != client->id) { continue; } cds_list_del(&client_list_element->node); free(client_list_element); break; } -end_unlock: - rcu_read_unlock(); + pthread_mutex_unlock(&client_list->lock); + notification_client_list_put(client_list); + client_list = NULL; end: lttng_condition_destroy(condition); if (_status) { @@ -941,29 +1228,33 @@ end: return 0; } +static +void free_notification_client_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct notification_client, rcu_node)); +} + static void notification_client_destroy(struct notification_client *client, struct notification_thread_state *state) { - struct lttng_condition_list_element *condition_list_element, *tmp; - if (!client) { return; } - /* Release all conditions to which the client was subscribed. */ - cds_list_for_each_entry_safe(condition_list_element, tmp, - &client->condition_list, node) { - (void) notification_thread_client_unsubscribe(client, - condition_list_element->condition, state, NULL); - } - + /* + * The client object is not reachable by other threads, no need to lock + * the client here. + */ if (client->socket >= 0) { (void) lttcomm_close_unix_sock(client->socket); + client->socket = -1; } - lttng_dynamic_buffer_reset(&client->communication.inbound.buffer); - lttng_dynamic_buffer_reset(&client->communication.outbound.buffer); - free(client); + client->communication.active = false; + lttng_payload_reset(&client->communication.inbound.payload); + lttng_payload_reset(&client->communication.outbound.payload); + pthread_mutex_destroy(&client->lock); + call_rcu(&client->rcu_node, free_notification_client_rcu); } /* @@ -979,8 +1270,8 @@ struct notification_client *get_client_from_socket(int socket, struct notification_client *client = NULL; cds_lfht_lookup(state->client_socket_ht, - hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed), - match_client, + hash_client_socket(socket), + match_client_socket, (void *) (unsigned long) socket, &iter); node = cds_lfht_iter_get_node(&iter); @@ -994,6 +1285,34 @@ end: return client; } +/* + * Call with rcu_read_lock held (and hold for the lifetime of the returned + * client pointer). + */ +static +struct notification_client *get_client_from_id(notification_client_id id, + struct notification_thread_state *state) +{ + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + struct notification_client *client = NULL; + + cds_lfht_lookup(state->client_id_ht, + hash_client_id(id), + match_client_id, + &id, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + goto end; + } + + client = caa_container_of(node, struct notification_client, + client_id_ht_node); +end: + return client; +} + static bool buffer_usage_condition_applies_to_channel( const struct lttng_condition *condition, @@ -1052,34 +1371,6 @@ fail: return false; } -/* - * Get the type of object to which a given trigger applies. Binding lets - * the notification system evaluate a trigger's condition when a given - * object's state is updated. - * - * For instance, a condition bound to a channel will be evaluated everytime - * the channel's state is changed by a channel monitoring sample. - */ -static -enum lttng_object_type get_trigger_binding_object( - const struct lttng_trigger *trigger) -{ - const struct lttng_condition *condition; - - condition = lttng_trigger_get_const_condition(trigger); - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - return LTTNG_OBJECT_TYPE_CHANNEL; - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: - return LTTNG_OBJECT_TYPE_SESSION; - default: - return LTTNG_OBJECT_TYPE_UNKNOWN; - } -} - static bool trigger_applies_to_channel(const struct lttng_trigger *trigger, const struct channel_info *channel_info) @@ -1127,17 +1418,40 @@ bool trigger_applies_to_client(struct lttng_trigger *trigger, break; } } - return applies; -} - -static -int match_session(struct cds_lfht_node *node, const void *key) -{ - const char *name = key; - struct session_info *session_info = caa_container_of( - node, struct session_info, sessions_ht_node); + return applies; +} + +/* Must be called with RCU read lock held. */ +static +struct lttng_session_trigger_list *get_session_trigger_list( + struct notification_thread_state *state, + const char *session_name) +{ + struct lttng_session_trigger_list *list = NULL; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + + cds_lfht_lookup(state->session_triggers_ht, + hash_key_str(session_name, lttng_ht_seed), + match_session_trigger_list, + session_name, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + /* + * Not an error, the list of triggers applying to that session + * will be initialized when the session is created. + */ + DBG("[notification-thread] No trigger list found for session \"%s\" as it is not yet known to the notification system", + session_name); + goto end; + } - return !strcmp(session_info->name, name); + list = caa_container_of(node, + struct lttng_session_trigger_list, + session_triggers_ht_node); +end: + return list; } /* @@ -1202,7 +1516,7 @@ void lttng_session_trigger_list_destroy(struct lttng_session_trigger_list *list) static int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, - const struct lttng_trigger *trigger) + struct lttng_trigger *trigger) { int ret = 0; struct lttng_trigger_list_element *new_element = @@ -1338,6 +1652,7 @@ struct session_info *find_or_create_session_info( if (!session) { ERR("[notification-thread] Failed to allocation session info for session \"%s\" (uid = %i, gid = %i)", name, uid, gid); + lttng_session_trigger_list_destroy(trigger_list); goto error; } trigger_list = NULL; @@ -1382,7 +1697,7 @@ int handle_notification_thread_command_add_channel( session_info = find_or_create_session_info(state, session_name, session_uid, session_gid); if (!session_info) { - /* Allocation error or an internal error occured. */ + /* Allocation error or an internal error occurred. */ goto error; } @@ -1392,6 +1707,7 @@ int handle_notification_thread_command_add_channel( goto error; } + rcu_read_lock(); /* Build a list of all triggers applying to the new channel. */ cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, node) { @@ -1404,6 +1720,7 @@ int handle_notification_thread_command_add_channel( new_element = zmalloc(sizeof(*new_element)); if (!new_element) { + rcu_read_unlock(); goto error; } CDS_INIT_LIST_HEAD(&new_element->node); @@ -1411,6 +1728,7 @@ int handle_notification_thread_command_add_channel( cds_list_add(&new_element->node, &trigger_list); trigger_count++; } + rcu_read_unlock(); DBG("[notification-thread] Found %i triggers that apply to newly added channel", trigger_count); @@ -1445,6 +1763,20 @@ error: return 1; } +static +void free_channel_trigger_list_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct lttng_channel_trigger_list, + rcu_node)); +} + +static +void free_channel_state_sample_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct channel_state_sample, + rcu_node)); +} + static int handle_notification_thread_command_remove_channel( struct notification_thread_state *state, @@ -1487,7 +1819,7 @@ int handle_notification_thread_command_remove_channel( free(trigger_list_element); } cds_lfht_del(state->channel_triggers_ht, node); - free(trigger_list); + call_rcu(&trigger_list->rcu_node, free_channel_trigger_list_rcu); /* Free sampled channel state. */ cds_lfht_lookup(state->channel_state_ht, @@ -1506,7 +1838,7 @@ int handle_notification_thread_command_remove_channel( channel_state_ht_node); cds_lfht_del(state->channel_state_ht, node); - free(sample); + call_rcu(&sample->rcu_node, free_channel_state_sample_rcu); } /* Remove the channel from the channels_ht and free it. */ @@ -1528,24 +1860,128 @@ end: } static -int handle_notification_thread_command_session_rotation_ongoing( +int handle_notification_thread_command_session_rotation( struct notification_thread_state *state, - const char *session_name, uint64_t trace_archive_chunk_id, - enum lttng_error_code *cmd_result) + enum notification_thread_command_type cmd_type, + const char *session_name, uid_t session_uid, gid_t session_gid, + uint64_t trace_archive_chunk_id, + struct lttng_trace_archive_location *location, + enum lttng_error_code *_cmd_result) { - *cmd_result = LTTNG_OK; - return 0; -} + int ret = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct lttng_session_trigger_list *trigger_list; + struct lttng_trigger_list_element *trigger_list_element; + struct session_info *session_info; + const struct lttng_credentials session_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(session_uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(session_gid), + }; -static -int handle_notification_thread_command_session_rotation_completed( - struct notification_thread_state *state, - const char *session_name, uint64_t trace_archive_chunk_id, - const struct lttng_trace_archive_location *location, - enum lttng_error_code *cmd_result) -{ - *cmd_result = LTTNG_OK; - return 0; + rcu_read_lock(); + + session_info = find_or_create_session_info(state, session_name, + session_uid, session_gid); + if (!session_info) { + /* Allocation error or an internal error occurred. */ + ret = -1; + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + + session_info->rotation.ongoing = + cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING; + session_info->rotation.id = trace_archive_chunk_id; + trigger_list = get_session_trigger_list(state, session_name); + if (!trigger_list) { + DBG("[notification-thread] No triggers applying to session \"%s\" found", + session_name); + goto end; + } + + cds_list_for_each_entry(trigger_list_element, &trigger_list->list, + node) { + const struct lttng_condition *condition; + struct lttng_trigger *trigger; + struct notification_client_list *client_list; + struct lttng_evaluation *evaluation = NULL; + enum lttng_condition_type condition_type; + enum action_executor_status executor_status; + + trigger = trigger_list_element->trigger; + condition = lttng_trigger_get_const_condition(trigger); + assert(condition); + condition_type = lttng_condition_get_type(condition); + + if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING && + cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { + continue; + } else if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED && + cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED) { + continue; + } + + client_list = get_client_list_from_condition(state, condition); + if (cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { + evaluation = lttng_evaluation_session_rotation_ongoing_create( + trace_archive_chunk_id); + } else { + evaluation = lttng_evaluation_session_rotation_completed_create( + trace_archive_chunk_id, location); + } + + if (!evaluation) { + /* Internal error */ + ret = -1; + cmd_result = LTTNG_ERR_UNK; + goto put_list; + } + + /* + * Ownership of `evaluation` transferred to the action executor + * no matter the result. + */ + executor_status = action_executor_enqueue(state->executor, + trigger, evaluation, &session_creds, + client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated with session rotation trigger"); + ret = -1; + goto put_list; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated with session rotation trigger"); + ret = 0; + goto put_list; + default: + abort(); + } + +put_list: + notification_client_list_put(client_list); + if (caa_unlikely(ret)) { + break; + } + } +end: + session_info_put(session_info); + *_cmd_result = cmd_result; + rcu_read_unlock(); + return ret; } static @@ -1576,8 +2012,7 @@ int condition_is_supported(struct lttng_condition *condition) * buffers. Therefore, we reject triggers that require that * mechanism to be available to be evaluated. */ - ret = kernel_supports_ring_buffer_snapshot_sample_positions( - kernel_tracer_fd); + ret = kernel_supports_ring_buffer_snapshot_sample_positions(); break; } default: @@ -1589,12 +2024,10 @@ end: /* Must be called with RCU read lock held. */ static -int bind_trigger_to_matching_session(const struct lttng_trigger *trigger, +int bind_trigger_to_matching_session(struct lttng_trigger *trigger, struct notification_thread_state *state) { int ret = 0; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; const struct lttng_condition *condition; const char *session_name; struct lttng_session_trigger_list *trigger_list; @@ -1620,25 +2053,13 @@ int bind_trigger_to_matching_session(const struct lttng_trigger *trigger, goto end; } - cds_lfht_lookup(state->session_triggers_ht, - hash_key_str(session_name, lttng_ht_seed), - match_session_trigger_list, - session_name, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - /* - * Not an error, the list of triggers applying to that session - * will be initialized when the session is created. - */ + trigger_list = get_session_trigger_list(state, session_name); + if (!trigger_list) { DBG("[notification-thread] Unable to bind trigger applying to session \"%s\" as it is not yet known to the notification system", session_name); goto end; - } - trigger_list = caa_container_of(node, - struct lttng_session_trigger_list, - session_triggers_ht_node); + } DBG("[notification-thread] Newly registered trigger bound to session \"%s\"", session_name); @@ -1649,7 +2070,7 @@ end: /* Must be called with RCU read lock held. */ static -int bind_trigger_to_matching_channels(const struct lttng_trigger *trigger, +int bind_trigger_to_matching_channels(struct lttng_trigger *trigger, struct notification_thread_state *state) { int ret = 0; @@ -1661,6 +2082,7 @@ int bind_trigger_to_matching_channels(const struct lttng_trigger *trigger, channels_ht_node) { struct lttng_trigger_list_element *trigger_list_element; struct lttng_channel_trigger_list *trigger_list; + struct cds_lfht_iter lookup_iter; if (!trigger_applies_to_channel(trigger, channel)) { continue; @@ -1670,8 +2092,8 @@ int bind_trigger_to_matching_channels(const struct lttng_trigger *trigger, hash_channel_key(&channel->key), match_channel_trigger_list, &channel->key, - &iter); - node = cds_lfht_iter_get_node(&iter); + &lookup_iter); + node = cds_lfht_iter_get_node(&lookup_iter); assert(node); trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, @@ -1692,9 +2114,90 @@ end: return ret; } +static +bool is_trigger_action_notify(const struct lttng_trigger *trigger) +{ + bool is_notify = false; + unsigned int i, count; + enum lttng_action_status action_status; + const struct lttng_action *action = + lttng_trigger_get_const_action(trigger); + enum lttng_action_type action_type; + + assert(action); + action_type = lttng_action_get_type(action); + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + is_notify = true; + goto end; + } else if (action_type != LTTNG_ACTION_TYPE_GROUP) { + goto end; + } + + action_status = lttng_action_group_get_count(action, &count); + assert(action_status == LTTNG_ACTION_STATUS_OK); + + for (i = 0; i < count; i++) { + const struct lttng_action *inner_action = + lttng_action_group_get_at_index( + action, i); + + action_type = lttng_action_get_type(inner_action); + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + is_notify = true; + goto end; + } + } + +end: + return is_notify; +} + +static bool trigger_name_taken(struct notification_thread_state *state, + const struct lttng_trigger *trigger) +{ + struct cds_lfht_iter iter; + + /* + * No duplicata is allowed in the triggers_by_name_uid_ht. + * The match is done against the trigger name and uid. + */ + cds_lfht_lookup(state->triggers_by_name_uid_ht, + hash_trigger_by_name_uid(trigger), + match_trigger_by_name_uid, + trigger, + &iter); + return !!cds_lfht_iter_get_node(&iter); +} + +static +enum lttng_error_code generate_trigger_name( + struct notification_thread_state *state, + struct lttng_trigger *trigger, const char **name) +{ + enum lttng_error_code ret_code = LTTNG_OK; + bool taken = false; + enum lttng_trigger_status status; + + do { + const int ret = lttng_trigger_generate_name(trigger, + state->trigger_id.name_offset++); + if (ret) { + /* The only reason this can fail right now. */ + ret_code = LTTNG_ERR_NOMEM; + break; + } + + status = lttng_trigger_get_name(trigger, name); + assert(status == LTTNG_TRIGGER_STATUS_OK); + + taken = trigger_name_taken(state, trigger); + } while (taken || state->trigger_id.name_offset == UINT64_MAX); + + return ret_code; +} + /* - * FIXME A client's credentials are not checked when registering a trigger, nor - * are they stored alongside with the trigger. + * FIXME A client's credentials are not checked when registering a trigger. * * The effects of this are benign since: * - The client will succeed in registering the trigger, as it is valid, @@ -1719,13 +2222,42 @@ int handle_notification_thread_command_register_trigger( struct notification_client *client; struct notification_client_list *client_list = NULL; struct lttng_trigger_ht_element *trigger_ht_element = NULL; - struct notification_client_list_element *client_list_element, *tmp; + struct notification_client_list_element *client_list_element; struct cds_lfht_node *node; struct cds_lfht_iter iter; + const char* trigger_name; bool free_trigger = true; + struct lttng_evaluation *evaluation = NULL; + struct lttng_credentials object_creds; + uid_t object_uid; + gid_t object_gid; + enum action_executor_status executor_status; + const uint64_t trigger_tracer_token = + state->trigger_id.next_tracer_token++; rcu_read_lock(); + /* Set the trigger's tracer token. */ + lttng_trigger_set_tracer_token(trigger, trigger_tracer_token); + + if (lttng_trigger_get_name(trigger, &trigger_name) == + LTTNG_TRIGGER_STATUS_UNSET) { + const enum lttng_error_code ret_code = generate_trigger_name( + state, trigger, &trigger_name); + + if (ret_code != LTTNG_OK) { + /* Fatal error. */ + ret = -1; + *cmd_result = ret_code; + goto error; + } + } else if (trigger_name_taken(state, trigger)) { + /* Not a fatal error. */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + ret = 0; + goto error; + } + condition = lttng_trigger_get_condition(trigger); assert(condition); @@ -1748,12 +2280,13 @@ int handle_notification_thread_command_register_trigger( /* Add trigger to the trigger_ht. */ cds_lfht_node_init(&trigger_ht_element->node); + cds_lfht_node_init(&trigger_ht_element->node_by_name_uid); trigger_ht_element->trigger = trigger; node = cds_lfht_add_unique(state->triggers_ht, lttng_condition_hash(condition), - match_condition, - condition, + match_trigger, + trigger, &trigger_ht_element->node); if (node != &trigger_ht_element->node) { /* Not a fatal error, simply report it to the client. */ @@ -1761,6 +2294,18 @@ int handle_notification_thread_command_register_trigger( goto error_free_ht_element; } + node = cds_lfht_add_unique(state->triggers_by_name_uid_ht, + hash_trigger_by_name_uid(trigger), + match_trigger_by_name_uid, + trigger, + &trigger_ht_element->node_by_name_uid); + if (node != &trigger_ht_element->node_by_name_uid) { + /* Not a fatal error, simply report it to the client. */ + cds_lfht_del(state->triggers_ht, &trigger_ht_element->node); + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + goto error_free_ht_element; + } + /* * Ownership of the trigger and of its wrapper was transfered to * the triggers_ht. @@ -1773,43 +2318,48 @@ int handle_notification_thread_command_register_trigger( * It is not skipped as this is the only action type currently * supported. */ - client_list = zmalloc(sizeof(*client_list)); - if (!client_list) { - ret = -1; - goto error_free_ht_element; - } - cds_lfht_node_init(&client_list->notification_trigger_ht_node); - CDS_INIT_LIST_HEAD(&client_list->list); - client_list->trigger = trigger; - - /* Build a list of clients to which this new trigger applies. */ - cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, - client_socket_ht_node) { - if (!trigger_applies_to_client(trigger, client)) { - continue; + if (is_trigger_action_notify(trigger)) { + client_list = notification_client_list_create(trigger); + if (!client_list) { + ret = -1; + goto error_free_ht_element; } - client_list_element = zmalloc(sizeof(*client_list_element)); - if (!client_list_element) { - ret = -1; - goto error_free_client_list; + /* Build a list of clients to which this new trigger applies. */ + cds_lfht_for_each_entry (state->client_socket_ht, &iter, client, + client_socket_ht_node) { + if (!trigger_applies_to_client(trigger, client)) { + continue; + } + + client_list_element = + zmalloc(sizeof(*client_list_element)); + if (!client_list_element) { + ret = -1; + goto error_put_client_list; + } + + CDS_INIT_LIST_HEAD(&client_list_element->node); + client_list_element->client = client; + cds_list_add(&client_list_element->node, + &client_list->list); } - CDS_INIT_LIST_HEAD(&client_list_element->node); - client_list_element->client = client; - cds_list_add(&client_list_element->node, &client_list->list); - } - cds_lfht_add(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - &client_list->notification_trigger_ht_node); + /* + * Client list ownership transferred to the + * notification_trigger_clients_ht. + */ + publish_notification_client_list(state, client_list); + } - switch (get_trigger_binding_object(trigger)) { + switch (get_condition_binding_object(condition)) { case LTTNG_OBJECT_TYPE_SESSION: /* Add the trigger to the list if it matches a known session. */ ret = bind_trigger_to_matching_session(trigger, state); if (ret) { - goto error_free_client_list; + goto error_put_client_list; } + break; case LTTNG_OBJECT_TYPE_CHANNEL: /* * Add the trigger to list of triggers bound to the channels @@ -1817,21 +2367,24 @@ int handle_notification_thread_command_register_trigger( */ ret = bind_trigger_to_matching_channels(trigger, state); if (ret) { - goto error_free_client_list; + goto error_put_client_list; } break; case LTTNG_OBJECT_TYPE_NONE: break; default: - ERR("[notification-thread] Unknown object type on which to bind a newly registered trigger was encountered"); + ERR("Unknown object type on which to bind a newly registered trigger was encountered"); ret = -1; - goto error_free_client_list; + goto error_put_client_list; } /* - * Since there is nothing preventing clients from subscribing to a - * condition before the corresponding trigger is registered, we have - * to evaluate this new condition right away. + * The new trigger's condition must be evaluated against the current + * state. + * + * In the case of `notify` action, nothing preventing clients from + * subscribing to a condition before the corresponding trigger is + * registered, we have to evaluate this new condition right away. * * At some point, we were waiting for the next "evaluation" (e.g. on * reception of a channel sample) to evaluate this new condition, but @@ -1854,44 +2407,105 @@ int handle_notification_thread_command_register_trigger( * that the evaluations remain the same (true for samples n-1 and n) and * the client will never know that the condition has been met. */ - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - ret = evaluate_condition_for_client(trigger, condition, - client_list_element->client, state); - if (ret) { - goto error_free_client_list; - } + 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; + break; + case LTTNG_OBJECT_TYPE_UNKNOWN: + default: + ret = -1; + break; + } + + if (ret) { + /* Fatal error. */ + goto error_put_client_list; + } + + LTTNG_OPTIONAL_SET(&object_creds.uid, object_uid); + LTTNG_OPTIONAL_SET(&object_creds.gid, object_gid); + + DBG("Newly registered trigger's condition evaluated to %s", + evaluation ? "true" : "false"); + if (!evaluation) { + /* Evaluation yielded nothing. Normal exit. */ + ret = 0; + goto end; } /* - * Client list ownership transferred to the - * notification_trigger_clients_ht. + * Ownership of `evaluation` transferred to the action executor + * no matter the result. */ - client_list = NULL; + executor_status = action_executor_enqueue(state->executor, trigger, + evaluation, &object_creds, client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated to newly registered trigger"); + ret = -1; + goto error_put_client_list; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated to newly registered trigger"); + ret = 0; + goto end; + default: + abort(); + } +end: *cmd_result = LTTNG_OK; -error_free_client_list: - if (client_list) { - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - free(client_list_element); - } - free(client_list); - } + DBG("Registered trigger: name = `%s`, tracer token = %" PRIu64, + trigger_name, trigger_tracer_token); + +error_put_client_list: + notification_client_list_put(client_list); + error_free_ht_element: - free(trigger_ht_element); + if (trigger_ht_element) { + /* Delayed removal due to RCU constraint on delete. */ + call_rcu(&trigger_ht_element->rcu_node, + free_lttng_trigger_ht_element_rcu); + } + error: if (free_trigger) { - struct lttng_action *action = lttng_trigger_get_action(trigger); - - lttng_condition_destroy(condition); - lttng_action_destroy(action); lttng_trigger_destroy(trigger); } rcu_read_unlock(); return ret; } +static +void free_lttng_trigger_ht_element_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct lttng_trigger_ht_element, + rcu_node)); +} + static int handle_notification_thread_command_unregister_trigger( struct notification_thread_state *state, @@ -1899,22 +2513,20 @@ int handle_notification_thread_command_unregister_trigger( enum lttng_error_code *_cmd_reply) { struct cds_lfht_iter iter; - struct cds_lfht_node *node, *triggers_ht_node; + struct cds_lfht_node *triggers_ht_node; struct lttng_channel_trigger_list *trigger_list; struct notification_client_list *client_list; - struct notification_client_list_element *client_list_element, *tmp; struct lttng_trigger_ht_element *trigger_ht_element = NULL; struct lttng_condition *condition = lttng_trigger_get_condition( trigger); - struct lttng_action *action; enum lttng_error_code cmd_reply; rcu_read_lock(); cds_lfht_lookup(state->triggers_ht, lttng_condition_hash(condition), - match_condition, - condition, + match_trigger, + trigger, &iter); triggers_ht_node = cds_lfht_iter_get_node(&iter); if (!triggers_ht_node) { @@ -1952,33 +2564,23 @@ int handle_notification_thread_command_unregister_trigger( * Remove and release the client list from * notification_trigger_clients_ht. */ - cds_lfht_lookup(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - match_client_list, - trigger, - &iter); - node = cds_lfht_iter_get_node(&iter); - assert(node); - client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - free(client_list_element); - } - cds_lfht_del(state->notification_trigger_clients_ht, node); - free(client_list); + client_list = get_client_list_from_condition(state, condition); + assert(client_list); + + /* Put new reference and the hashtable's reference. */ + notification_client_list_put(client_list); + notification_client_list_put(client_list); + client_list = NULL; /* Remove trigger from triggers_ht. */ trigger_ht_element = caa_container_of(triggers_ht_node, struct lttng_trigger_ht_element, node); + cds_lfht_del(state->triggers_by_name_uid_ht, &trigger_ht_element->node_by_name_uid); cds_lfht_del(state->triggers_ht, triggers_ht_node); - condition = lttng_trigger_get_condition(trigger_ht_element->trigger); - lttng_condition_destroy(condition); - action = lttng_trigger_get_action(trigger_ht_element->trigger); - lttng_action_destroy(action); + /* Release the ownership of the trigger. */ lttng_trigger_destroy(trigger_ht_element->trigger); - free(trigger_ht_element); + call_rcu(&trigger_ht_element->rcu_node, free_lttng_trigger_ht_element_rcu); end: rcu_read_unlock(); if (_cmd_reply) { @@ -1997,15 +2599,17 @@ int handle_notification_thread_command( struct notification_thread_command *cmd; /* Read the event pipe to put it back into a quiescent state. */ - ret = read(lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), &counter, + ret = lttng_read(lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), &counter, sizeof(counter)); - if (ret == -1) { + if (ret != sizeof(counter)) { goto error; } pthread_mutex_lock(&handle->cmd_queue.lock); cmd = cds_list_first_entry(&handle->cmd_queue.list, struct notification_thread_command, cmd_list_node); + cds_list_del(&cmd->cmd_list_node); + pthread_mutex_unlock(&handle->cmd_queue.lock); switch (cmd->type) { case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: DBG("[notification-thread] Received register trigger command"); @@ -2040,20 +2644,18 @@ int handle_notification_thread_command( &cmd->reply_code); break; case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING: - DBG("[notification-thread] Received session rotation ongoing command"); - ret = handle_notification_thread_command_session_rotation_ongoing( - state, - cmd->parameters.session_rotation_ongoing.session_name, - cmd->parameters.session_rotation_ongoing.trace_archive_chunk_id, - &cmd->reply_code); - break; case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED: - DBG("[notification-thread] Received session rotation completed command"); - ret = handle_notification_thread_command_session_rotation_completed( + DBG("[notification-thread] Received session rotation %s command", + cmd->type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING ? + "ongoing" : "completed"); + ret = handle_notification_thread_command_session_rotation( state, - cmd->parameters.session_rotation_completed.session_name, - cmd->parameters.session_rotation_completed.trace_archive_chunk_id, - cmd->parameters.session_rotation_completed.location, + cmd->type, + cmd->parameters.session_rotation.session_name, + cmd->parameters.session_rotation.uid, + cmd->parameters.session_rotation.gid, + cmd->parameters.session_rotation.trace_archive_chunk_id, + cmd->parameters.session_rotation.location, &cmd->reply_code); break; case NOTIFICATION_COMMAND_TYPE_QUIT: @@ -2061,6 +2663,34 @@ int handle_notification_thread_command( cmd->reply_code = LTTNG_OK; ret = 1; goto end; + case NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE: + { + const enum client_transmission_status client_status = + cmd->parameters.client_communication_update + .status; + const notification_client_id client_id = + cmd->parameters.client_communication_update.id; + struct notification_client *client; + + rcu_read_lock(); + client = get_client_from_id(client_id, state); + + if (!client) { + /* + * Client error was probably already picked-up by the + * notification thread or it has disconnected + * gracefully while this command was queued. + */ + DBG("Failed to find notification client to update communication status, client id = %" PRIu64, + client_id); + ret = 0; + } else { + ret = client_handle_transmission_status( + client, client_status, state); + } + rcu_read_unlock(); + break; + } default: ERR("[notification-thread] Unknown internal command received"); goto error_unlock; @@ -2070,26 +2700,22 @@ int handle_notification_thread_command( goto error_unlock; } end: - cds_list_del(&cmd->cmd_list_node); - lttng_waiter_wake_up(&cmd->reply_waiter); - pthread_mutex_unlock(&handle->cmd_queue.lock); + if (cmd->is_async) { + free(cmd); + cmd = NULL; + } else { + lttng_waiter_wake_up(&cmd->reply_waiter); + } return ret; error_unlock: /* Wake-up and return a fatal error to the calling thread. */ lttng_waiter_wake_up(&cmd->reply_waiter); - pthread_mutex_unlock(&handle->cmd_queue.lock); cmd->reply_code = LTTNG_ERR_FATAL; error: /* Indicate a fatal error to the caller. */ return -1; } -static -unsigned long hash_client_socket(int socket) -{ - return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed); -} - static int socket_set_non_blocking(int socket) { @@ -2118,9 +2744,8 @@ int client_reset_inbound_state(struct notification_client *client) { int ret; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, 0); - assert(!ret); + + lttng_payload_clear(&client->communication.inbound.payload); client->communication.inbound.bytes_to_receive = sizeof(struct lttng_notification_channel_message); @@ -2129,8 +2754,9 @@ int client_reset_inbound_state(struct notification_client *client) LTTNG_SOCK_SET_UID_CRED(&client->communication.inbound.creds, -1); LTTNG_SOCK_SET_GID_CRED(&client->communication.inbound.creds, -1); ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, + &client->communication.inbound.payload.buffer, client->communication.inbound.bytes_to_receive); + return ret; } @@ -2148,14 +2774,18 @@ int handle_notification_thread_client_connect( ret = -1; goto error; } + + pthread_mutex_init(&client->lock, NULL); + client->id = state->next_notification_client_id++; CDS_INIT_LIST_HEAD(&client->condition_list); - lttng_dynamic_buffer_init(&client->communication.inbound.buffer); - lttng_dynamic_buffer_init(&client->communication.outbound.buffer); + lttng_payload_init(&client->communication.inbound.payload); + lttng_payload_init(&client->communication.outbound.payload); client->communication.inbound.expect_creds = true; + ret = client_reset_inbound_state(client); if (ret) { ERR("[notification-thread] Failed to reset client communication's inbound state"); - ret = 0; + ret = 0; goto error; } @@ -2196,17 +2826,60 @@ int handle_notification_thread_client_connect( cds_lfht_add(state->client_socket_ht, hash_client_socket(client->socket), &client->client_socket_ht_node); + cds_lfht_add(state->client_id_ht, + hash_client_id(client->id), + &client->client_id_ht_node); rcu_read_unlock(); return ret; + error: notification_client_destroy(client, state); return ret; } -int handle_notification_thread_client_disconnect( - int client_socket, +/* + * RCU read-lock must be held by the caller. + * Client lock must _not_ be held by the caller. + */ +static +int notification_thread_client_disconnect( + struct notification_client *client, struct notification_thread_state *state) +{ + int ret; + struct lttng_condition_list_element *condition_list_element, *tmp; + + /* Acquire the client lock to disable its communication atomically. */ + pthread_mutex_lock(&client->lock); + client->communication.active = false; + cds_lfht_del(state->client_socket_ht, &client->client_socket_ht_node); + cds_lfht_del(state->client_id_ht, &client->client_id_ht_node); + pthread_mutex_unlock(&client->lock); + + ret = lttng_poll_del(&state->events, client->socket); + if (ret) { + ERR("[notification-thread] Failed to remove client socket %d from poll set", + client->socket); + } + + /* Release all conditions to which the client was subscribed. */ + cds_list_for_each_entry_safe(condition_list_element, tmp, + &client->condition_list, node) { + (void) notification_thread_client_unsubscribe(client, + condition_list_element->condition, state, NULL); + } + + /* + * Client no longer accessible to other threads (through the + * client lists). + */ + notification_client_destroy(client, state); + return ret; +} + +int handle_notification_thread_client_disconnect( + int client_socket, struct notification_thread_state *state) { int ret = 0; struct notification_client *client; @@ -2223,13 +2896,7 @@ int handle_notification_thread_client_disconnect( goto end; } - ret = lttng_poll_del(&state->events, client_socket); - if (ret) { - ERR("[notification-thread] Failed to remove client socket from poll set"); - } - cds_lfht_del(state->client_socket_ht, - &client->client_socket_ht_node); - notification_client_destroy(client, state); + ret = notification_thread_client_disconnect(client, state); end: rcu_read_unlock(); return ret; @@ -2245,11 +2912,11 @@ int handle_notification_thread_client_disconnect_all( rcu_read_lock(); DBG("[notification-thread] Closing all client connections"); cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, - client_socket_ht_node) { + client_socket_ht_node) { int ret; - ret = handle_notification_thread_client_disconnect( - client->socket, state); + ret = notification_thread_client_disconnect( + client, state); if (ret) { error_encoutered = true; } @@ -2265,6 +2932,7 @@ int handle_notification_thread_trigger_unregister_all( struct cds_lfht_iter iter; struct lttng_trigger_ht_element *trigger_ht_element; + rcu_read_lock(); cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, node) { int ret = handle_notification_thread_command_unregister_trigger( @@ -2273,126 +2941,430 @@ int handle_notification_thread_trigger_unregister_all( error_occurred = true; } } + rcu_read_unlock(); return error_occurred ? -1 : 0; } static -int client_flush_outgoing_queue(struct notification_client *client, +int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status transmission_status, struct notification_thread_state *state) +{ + int ret = 0; + + switch (transmission_status) { + case CLIENT_TRANSMISSION_STATUS_COMPLETE: + ret = lttng_poll_mod(&state->events, client->socket, + CLIENT_POLL_MASK_IN); + if (ret) { + goto end; + } + + break; + case CLIENT_TRANSMISSION_STATUS_QUEUED: + /* + * We want to be notified whenever there is buffer space + * available to send the rest of the payload. + */ + ret = lttng_poll_mod(&state->events, client->socket, + CLIENT_POLL_MASK_IN_OUT); + if (ret) { + goto end; + } + break; + case CLIENT_TRANSMISSION_STATUS_FAIL: + ret = notification_thread_client_disconnect(client, state); + if (ret) { + goto end; + } + break; + case CLIENT_TRANSMISSION_STATUS_ERROR: + ret = -1; + goto end; + default: + abort(); + } +end: + return ret; +} + +/* Client lock must be acquired by caller. */ +static +enum client_transmission_status client_flush_outgoing_queue( + struct notification_client *client) { ssize_t ret; size_t to_send_count; + enum client_transmission_status status; + struct lttng_payload_view pv = lttng_payload_view_from_payload( + &client->communication.outbound.payload, 0, -1); + const int fds_to_send_count = + lttng_payload_view_get_fd_handle_count(&pv); + + ASSERT_LOCKED(client->lock); - assert(client->communication.outbound.buffer.size != 0); - to_send_count = client->communication.outbound.buffer.size; + if (!client->communication.active) { + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } + + if (pv.buffer.size == 0) { + /* + * If both data and fds are equal to zero, we are in an invalid + * state. + */ + assert(fds_to_send_count != 0); + goto send_fds; + } + + /* Send data. */ + to_send_count = pv.buffer.size; DBG("[notification-thread] Flushing client (socket fd = %i) outgoing queue", client->socket); ret = lttcomm_send_unix_sock_non_block(client->socket, - client->communication.outbound.buffer.data, + pv.buffer.data, to_send_count); - if ((ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) || - (ret > 0 && ret < to_send_count)) { + if ((ret >= 0 && ret < to_send_count)) { DBG("[notification-thread] Client (socket fd = %i) outgoing queue could not be completely flushed", client->socket); to_send_count -= max(ret, 0); - memcpy(client->communication.outbound.buffer.data, - client->communication.outbound.buffer.data + - client->communication.outbound.buffer.size - to_send_count, + memmove(client->communication.outbound.payload.buffer.data, + pv.buffer.data + + pv.buffer.size - to_send_count, to_send_count); ret = lttng_dynamic_buffer_set_size( - &client->communication.outbound.buffer, + &client->communication.outbound.payload.buffer, to_send_count); if (ret) { goto error; } - /* - * We want to be notified whenever there is buffer space - * available to send the rest of the payload. - */ - ret = lttng_poll_mod(&state->events, client->socket, - CLIENT_POLL_MASK_IN_OUT); - if (ret) { - goto error; - } + status = CLIENT_TRANSMISSION_STATUS_QUEUED; + goto end; } else if (ret < 0) { - /* Generic error, disconnect the client. */ - ERR("[notification-thread] Failed to send flush outgoing queue, disconnecting client (socket fd = %i)", + /* Generic error, disable the client's communication. */ + ERR("[notification-thread] Failed to flush outgoing queue, disconnecting client (socket fd = %i)", client->socket); - ret = handle_notification_thread_client_disconnect( - client->socket, state); - if (ret) { - goto error; - } + client->communication.active = false; + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; } else { - /* No error and flushed the queue completely. */ + /* + * No error and flushed the queue completely. + * + * The payload buffer size is used later to + * check if there is notifications queued. So albeit that the + * direct caller knows that the transmission is complete, we + * need to set the buffer size to zero. + */ ret = lttng_dynamic_buffer_set_size( - &client->communication.outbound.buffer, 0); - if (ret) { - goto error; - } - ret = lttng_poll_mod(&state->events, client->socket, - CLIENT_POLL_MASK_IN); + &client->communication.outbound.payload.buffer, 0); if (ret) { goto error; } + } + +send_fds: + /* No fds to send, transmission is complete. */ + if (fds_to_send_count == 0) { + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; + goto end; + } + + ret = lttcomm_send_payload_view_fds_unix_sock_non_block( + client->socket, &pv); + if (ret < 0) { + /* Generic error, disable the client's communication. */ + ERR("[notification-thread] Failed to flush outgoing fds queue, disconnecting client (socket fd = %i)", + client->socket); + client->communication.active = false; + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } else if (ret == 0) { + /* Nothing could be sent. */ + status = CLIENT_TRANSMISSION_STATUS_QUEUED; + } else { + /* Fd passing is an all or nothing kind of thing. */ + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; + /* + * The payload _fd_array count is used later to + * check if there is notifications queued. So although the + * direct caller knows that the transmission is complete, we + * need to clear the _fd_array for the queuing check. + */ + lttng_dynamic_pointer_array_clear( + &client->communication.outbound.payload + ._fd_handles); + } + +end: + if (status == CLIENT_TRANSMISSION_STATUS_COMPLETE) { + client->communication.outbound.queued_command_reply = false; + client->communication.outbound.dropped_notification = false; + lttng_payload_clear(&client->communication.outbound.payload); + } + + return status; +error: + return CLIENT_TRANSMISSION_STATUS_ERROR; +} + +static +bool client_has_outbound_data_left( + const struct notification_client *client) +{ + const struct lttng_payload_view pv = lttng_payload_view_from_payload( + &client->communication.outbound.payload, 0, -1); + const bool has_data = pv.buffer.size != 0; + const bool has_fds = lttng_payload_view_get_fd_handle_count(&pv); + + return has_data || has_fds; +} + +/* Client lock must _not_ be held by the caller. */ +static +int client_send_command_reply(struct notification_client *client, + struct notification_thread_state *state, + enum lttng_notification_channel_status status) +{ + int ret; + struct lttng_notification_channel_command_reply reply = { + .status = (int8_t) status, + }; + struct lttng_notification_channel_message msg = { + .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_COMMAND_REPLY, + .size = sizeof(reply), + }; + char buffer[sizeof(msg) + sizeof(reply)]; + enum client_transmission_status transmission_status; + + memcpy(buffer, &msg, sizeof(msg)); + memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); + DBG("[notification-thread] Send command reply (%i)", (int) status); + + pthread_mutex_lock(&client->lock); + if (client->communication.outbound.queued_command_reply) { + /* Protocol error. */ + goto error_unlock; + } + + /* Enqueue buffer to outgoing queue and flush it. */ + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.payload.buffer, + buffer, sizeof(buffer)); + if (ret) { + goto error_unlock; + } + + transmission_status = client_flush_outgoing_queue(client); + + if (client_has_outbound_data_left(client)) { + /* Queue could not be emptied. */ + client->communication.outbound.queued_command_reply = true; + } + + pthread_mutex_unlock(&client->lock); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto error; + } + + return 0; +error_unlock: + pthread_mutex_unlock(&client->lock); +error: + return -1; +} + +static +int client_handle_message_unknown(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + /* + * Receiving message header. The function will be called again + * once the rest of the message as been received and can be + * interpreted. + */ + const struct lttng_notification_channel_message *msg; + + assert(sizeof(*msg) == client->communication.inbound.payload.buffer.size); + msg = (const struct lttng_notification_channel_message *) + client->communication.inbound.payload.buffer.data; + + if (msg->size == 0 || + msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { + ERR("[notification-thread] Invalid notification channel message: length = %u", + msg->size); + ret = -1; + goto end; + } + + switch (msg->type) { + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: + break; + default: + ret = -1; + ERR("[notification-thread] Invalid notification channel message: unexpected message type"); + goto end; + } + + client->communication.inbound.bytes_to_receive = msg->size; + client->communication.inbound.fds_to_receive = msg->fds; + client->communication.inbound.msg_type = + (enum lttng_notification_channel_message_type) msg->type; + ret = lttng_dynamic_buffer_set_size( + &client->communication.inbound.payload.buffer, msg->size); + + /* msg is not valid anymore due to lttng_dynamic_buffer_set_size. */ + msg = NULL; +end: + return ret; +} + +static +int client_handle_message_handshake(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + struct lttng_notification_channel_command_handshake *handshake_client; + const struct lttng_notification_channel_command_handshake handshake_reply = { + .major = LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR, + .minor = LTTNG_NOTIFICATION_CHANNEL_VERSION_MINOR, + }; + const struct lttng_notification_channel_message msg_header = { + .type = LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE, + .size = sizeof(handshake_reply), + }; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + char send_buffer[sizeof(msg_header) + sizeof(handshake_reply)]; + + memcpy(send_buffer, &msg_header, sizeof(msg_header)); + memcpy(send_buffer + sizeof(msg_header), &handshake_reply, + sizeof(handshake_reply)); + + handshake_client = + (struct lttng_notification_channel_command_handshake *) + client->communication.inbound.payload.buffer + .data; + client->major = handshake_client->major; + client->minor = handshake_client->minor; + if (!client->communication.inbound.creds_received) { + ERR("[notification-thread] No credentials received from client"); + ret = -1; + goto end; + } - client->communication.outbound.queued_command_reply = false; - client->communication.outbound.dropped_notification = false; + client->uid = LTTNG_SOCK_GET_UID_CRED( + &client->communication.inbound.creds); + client->gid = LTTNG_SOCK_GET_GID_CRED( + &client->communication.inbound.creds); + DBG("[notification-thread] Received handshake from client (uid = %u, gid = %u) with version %i.%i", + client->uid, client->gid, (int) client->major, + (int) client->minor); + + if (handshake_client->major != + LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { + status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; } - return 0; -error: - return -1; + pthread_mutex_lock(&client->lock); + /* Outgoing queue will be flushed when the command reply is sent. */ + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.payload.buffer, send_buffer, + sizeof(send_buffer)); + if (ret) { + ERR("[notification-thread] Failed to send protocol version to notification channel client"); + goto end_unlock; + } + + client->validated = true; + client->communication.active = true; + pthread_mutex_unlock(&client->lock); + + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end; + } + + /* Flushes the outgoing queue. */ + ret = client_send_command_reply(client, state, status); + if (ret) { + ERR("[notification-thread] Failed to send reply to notification channel client"); + goto end; + } + + goto end; +end_unlock: + pthread_mutex_unlock(&client->lock); +end: + return ret; } static -int client_send_command_reply(struct notification_client *client, - struct notification_thread_state *state, - enum lttng_notification_channel_status status) +int client_handle_message_subscription( + struct notification_client *client, + enum lttng_notification_channel_message_type msg_type, + struct notification_thread_state *state) { int ret; - struct lttng_notification_channel_command_reply reply = { - .status = (int8_t) status, - }; - struct lttng_notification_channel_message msg = { - .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_COMMAND_REPLY, - .size = sizeof(reply), - }; - char buffer[sizeof(msg) + sizeof(reply)]; + struct lttng_condition *condition; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + struct lttng_payload_view condition_view = + lttng_payload_view_from_payload( + &client->communication.inbound.payload, + 0, -1); + size_t expected_condition_size; - if (client->communication.outbound.queued_command_reply) { - /* Protocol error. */ - goto error; + /* + * No need to lock client to sample the inbound state as the only + * other thread accessing clients (action executor) only uses the + * outbound state. + */ + expected_condition_size = client->communication.inbound.payload.buffer.size; + ret = lttng_condition_create_from_payload(&condition_view, &condition); + if (ret != expected_condition_size) { + ERR("[notification-thread] Malformed condition received from client"); + goto end; } - memcpy(buffer, &msg, sizeof(msg)); - memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); - DBG("[notification-thread] Send command reply (%i)", (int) status); + if (msg_type == LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE) { + ret = notification_thread_client_subscribe( + client, condition, state, &status); + } else { + ret = notification_thread_client_unsubscribe( + client, condition, state, &status); + } - /* Enqueue buffer to outgoing queue and flush it. */ - ret = lttng_dynamic_buffer_append( - &client->communication.outbound.buffer, - buffer, sizeof(buffer)); if (ret) { - goto error; + goto end; } - ret = client_flush_outgoing_queue(client, state); + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); if (ret) { - goto error; + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end; } - if (client->communication.outbound.buffer.size != 0) { - /* Queue could not be emptied. */ - client->communication.outbound.queued_command_reply = true; + ret = client_send_command_reply(client, state, status); + if (ret) { + ERR("[notification-thread] Failed to send reply to notification channel client"); + goto end; } - return 0; -error: - return -1; +end: + return ret; } static @@ -2414,158 +3386,19 @@ int client_dispatch_message(struct notification_client *client, switch (client->communication.inbound.msg_type) { case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN: { - /* - * Receiving message header. The function will be called again - * once the rest of the message as been received and can be - * interpreted. - */ - const struct lttng_notification_channel_message *msg; - - assert(sizeof(*msg) == - client->communication.inbound.buffer.size); - msg = (const struct lttng_notification_channel_message *) - client->communication.inbound.buffer.data; - - if (msg->size == 0 || msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { - ERR("[notification-thread] Invalid notification channel message: length = %u", msg->size); - ret = -1; - goto end; - } - - switch (msg->type) { - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: - break; - default: - ret = -1; - ERR("[notification-thread] Invalid notification channel message: unexpected message type"); - goto end; - } - - client->communication.inbound.bytes_to_receive = msg->size; - client->communication.inbound.msg_type = - (enum lttng_notification_channel_message_type) msg->type; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, msg->size); - if (ret) { - goto end; - } + ret = client_handle_message_unknown(client, state); break; } case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: { - struct lttng_notification_channel_command_handshake *handshake_client; - struct lttng_notification_channel_command_handshake handshake_reply = { - .major = LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR, - .minor = LTTNG_NOTIFICATION_CHANNEL_VERSION_MINOR, - }; - struct lttng_notification_channel_message msg_header = { - .type = LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE, - .size = sizeof(handshake_reply), - }; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - char send_buffer[sizeof(msg_header) + sizeof(handshake_reply)]; - - memcpy(send_buffer, &msg_header, sizeof(msg_header)); - memcpy(send_buffer + sizeof(msg_header), &handshake_reply, - sizeof(handshake_reply)); - - handshake_client = - (struct lttng_notification_channel_command_handshake *) - client->communication.inbound.buffer.data; - client->major = handshake_client->major; - client->minor = handshake_client->minor; - if (!client->communication.inbound.creds_received) { - ERR("[notification-thread] No credentials received from client"); - ret = -1; - goto end; - } - - client->uid = LTTNG_SOCK_GET_UID_CRED( - &client->communication.inbound.creds); - client->gid = LTTNG_SOCK_GET_GID_CRED( - &client->communication.inbound.creds); - DBG("[notification-thread] Received handshake from client (uid = %u, gid = %u) with version %i.%i", - client->uid, client->gid, (int) client->major, - (int) client->minor); - - if (handshake_client->major != LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { - status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; - } - - ret = lttng_dynamic_buffer_append(&client->communication.outbound.buffer, - send_buffer, sizeof(send_buffer)); - if (ret) { - ERR("[notification-thread] Failed to send protocol version to notification channel client"); - goto end; - } - - ret = client_flush_outgoing_queue(client, state); - if (ret) { - goto end; - } - - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("[notification-thread] Failed to send reply to notification channel client"); - goto end; - } - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("[notification-thread] Failed to reset client communication's inbound state"); - goto end; - } - client->validated = true; + ret = client_handle_message_handshake(client, state); break; } case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: { - struct lttng_condition *condition; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - const struct lttng_buffer_view condition_view = - lttng_buffer_view_from_dynamic_buffer( - &client->communication.inbound.buffer, - 0, -1); - size_t expected_condition_size = - client->communication.inbound.buffer.size; - - ret = lttng_condition_create_from_buffer(&condition_view, - &condition); - if (ret != expected_condition_size) { - ERR("[notification-thread] Malformed condition received from client"); - goto end; - } - - if (client->communication.inbound.msg_type == - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE) { - ret = notification_thread_client_subscribe(client, - condition, state, &status); - } else { - ret = notification_thread_client_unsubscribe(client, - condition, state, &status); - } - if (ret) { - goto end; - } - - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("[notification-thread] Failed to send reply to notification channel client"); - goto end; - } - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("[notification-thread] Failed to reset client communication's inbound state"); - goto end; - } + ret = client_handle_message_subscription(client, + client->communication.inbound.msg_type, state); break; } default: @@ -2584,6 +3417,7 @@ int handle_notification_thread_client_in( ssize_t recv_ret; size_t offset; + rcu_read_lock(); client = get_client_from_socket(socket, state); if (!client) { /* Internal error, abort. */ @@ -2591,11 +3425,11 @@ int handle_notification_thread_client_in( goto end; } - offset = client->communication.inbound.buffer.size - + offset = client->communication.inbound.payload.buffer.size - client->communication.inbound.bytes_to_receive; if (client->communication.inbound.expect_creds) { recv_ret = lttcomm_recv_creds_unix_sock(socket, - client->communication.inbound.buffer.data + offset, + client->communication.inbound.payload.buffer.data + offset, client->communication.inbound.bytes_to_receive, &client->communication.inbound.creds); if (recv_ret > 0) { @@ -2604,31 +3438,69 @@ int handle_notification_thread_client_in( } } else { recv_ret = lttcomm_recv_unix_sock_non_block(socket, - client->communication.inbound.buffer.data + offset, + client->communication.inbound.payload.buffer.data + offset, client->communication.inbound.bytes_to_receive); } - if (recv_ret < 0) { + if (recv_ret >= 0) { + client->communication.inbound.bytes_to_receive -= recv_ret; + } else { goto error_disconnect_client; } - client->communication.inbound.bytes_to_receive -= recv_ret; - if (client->communication.inbound.bytes_to_receive == 0) { - ret = client_dispatch_message(client, state); - if (ret) { + if (client->communication.inbound.bytes_to_receive != 0) { + /* Message incomplete wait for more data. */ + ret = 0; + goto end; + } + + assert(client->communication.inbound.bytes_to_receive == 0); + + /* Receive fds. */ + if (client->communication.inbound.fds_to_receive != 0) { + ret = lttcomm_recv_payload_fds_unix_sock_non_block( + client->socket, + client->communication.inbound.fds_to_receive, + &client->communication.inbound.payload); + if (ret > 0) { /* - * Only returns an error if this client must be - * disconnected. + * Fds received. non blocking fds passing is all + * or nothing. */ + ssize_t expected_size; + + expected_size = sizeof(int) * + client->communication.inbound + .fds_to_receive; + assert(ret == expected_size); + client->communication.inbound.fds_to_receive = 0; + } else if (ret == 0) { + /* Received nothing. */ + ret = 0; + goto end; + } else { goto error_disconnect_client; } - } else { - goto end; } + + /* At this point the message is complete.*/ + assert(client->communication.inbound.bytes_to_receive == 0 && + client->communication.inbound.fds_to_receive == 0); + ret = client_dispatch_message(client, state); + if (ret) { + /* + * Only returns an error if this client must be + * disconnected. + */ + goto error_disconnect_client; + } + end: + rcu_read_unlock(); return ret; + error_disconnect_client: - ret = handle_notification_thread_client_disconnect(socket, state); - return ret; + ret = notification_thread_client_disconnect(client, state); + goto end; } /* Client ready to receive outgoing data. */ @@ -2637,7 +3509,9 @@ int handle_notification_thread_client_out( { int ret; struct notification_client *client; + enum client_transmission_status transmission_status; + rcu_read_lock(); client = get_client_from_socket(socket, state); if (!client) { /* Internal error, abort. */ @@ -2645,11 +3519,17 @@ int handle_notification_thread_client_out( goto end; } - ret = client_flush_outgoing_queue(client, state); + pthread_mutex_lock(&client->lock); + transmission_status = client_flush_outgoing_queue(client); + pthread_mutex_unlock(&client->lock); + + ret = client_handle_transmission_status( + client, transmission_status, state); if (ret) { goto end; } end: + rcu_read_unlock(); return ret; } @@ -2676,7 +3556,7 @@ bool evaluate_buffer_usage_condition(const struct lttng_condition *condition, * forego this double-multiplication or it could be performed * as fixed-point math. * - * Note that caching should accomodate the case where the + * Note that caching should accommodates the case where the * condition applies to multiple channels (i.e. don't assume * that all channels matching my_chann* have the same size...) */ @@ -2730,7 +3610,7 @@ bool evaluate_session_consumed_size_condition( } static -int evaluate_condition(const struct lttng_condition *condition, +int evaluate_buffer_condition(const struct lttng_condition *condition, struct lttng_evaluation **evaluation, const struct notification_thread_state *state, const struct channel_state_sample *previous_sample, @@ -2816,48 +3696,129 @@ end: } static -int client_enqueue_dropped_notification(struct notification_client *client, - struct notification_thread_state *state) +int client_notification_overflow(struct notification_client *client) { - int ret; - struct lttng_notification_channel_message msg = { + int ret = 0; + const struct lttng_notification_channel_message msg = { .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION_DROPPED, - .size = 0, }; + ASSERT_LOCKED(client->lock); + + DBG("Dropping notification addressed to client (socket fd = %i)", + client->socket); + if (client->communication.outbound.dropped_notification) { + /* + * The client already has a "notification dropped" message + * in its outgoing queue. Nothing to do since all + * of those messages are coalesced. + */ + goto end; + } + + client->communication.outbound.dropped_notification = true; ret = lttng_dynamic_buffer_append( - &client->communication.outbound.buffer, &msg, + &client->communication.outbound.payload.buffer, &msg, sizeof(msg)); + if (ret) { + PERROR("Failed to enqueue \"dropped notification\" message in client's (socket fd = %i) outgoing queue", + client->socket); + } +end: return ret; } +static int client_handle_transmission_status_wrapper( + struct notification_client *client, + enum client_transmission_status status, + void *user_data) +{ + return client_handle_transmission_status(client, status, + (struct notification_thread_state *) user_data); +} + static int send_evaluation_to_clients(const struct lttng_trigger *trigger, const struct lttng_evaluation *evaluation, struct notification_client_list* client_list, struct notification_thread_state *state, - uid_t channel_uid, gid_t channel_gid) + uid_t object_uid, gid_t object_gid) +{ + const struct lttng_credentials creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(object_uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(object_gid), + }; + + return notification_client_list_send_evaluation(client_list, + lttng_trigger_get_const_condition(trigger), evaluation, + lttng_trigger_get_credentials(trigger), + &creds, + client_handle_transmission_status_wrapper, state); +} + +/* + * Permission checks relative to notification channel clients are performed + * here. Notice how object, client, and trigger credentials are involved in + * this check. + * + * The `object` credentials are the credentials associated with the "subject" + * of a condition. For instance, a `rotation completed` condition applies + * to a session. When that condition is met, it will produce an evaluation + * against a session. Hence, in this case, the `object` credentials are the + * credentials of the "subject" session. + * + * The `trigger` credentials are the credentials of the user that registered the + * trigger. + * + * The `client` credentials are the credentials of the user that created a given + * notification channel. + * + * In terms of visibility, it is expected that non-privilieged users can only + * register triggers against "their" objects (their own sessions and + * applications they are allowed to interact with). They can then open a + * notification channel and subscribe to notifications associated with those + * triggers. + * + * As for privilieged users, they can register triggers against the objects of + * other users. They can then subscribe to the notifications associated to their + * triggers. Privilieged users _can't_ subscribe to the notifications of + * triggers owned by other users; they must create their own triggers. + * + * This is more a concern of usability than security. It would be difficult for + * a root user reliably subscribe to a specific set of conditions without + * interference from external users (those could, for instance, unregister + * their triggers). + */ +LTTNG_HIDDEN +int notification_client_list_send_evaluation( + struct notification_client_list *client_list, + const struct lttng_condition *condition, + const struct lttng_evaluation *evaluation, + const struct lttng_credentials *trigger_creds, + const struct lttng_credentials *source_object_creds, + report_client_transmission_result_cb client_report, + void *user_data) { int ret = 0; - struct lttng_dynamic_buffer msg_buffer; + struct lttng_payload msg_payload; struct notification_client_list_element *client_list_element, *tmp; const struct lttng_notification notification = { - .condition = (struct lttng_condition *) lttng_trigger_get_const_condition(trigger), + .condition = (struct lttng_condition *) condition, .evaluation = (struct lttng_evaluation *) evaluation, }; struct lttng_notification_channel_message msg_header = { .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION, }; - lttng_dynamic_buffer_init(&msg_buffer); + lttng_payload_init(&msg_payload); - ret = lttng_dynamic_buffer_append(&msg_buffer, &msg_header, + ret = lttng_dynamic_buffer_append(&msg_payload.buffer, &msg_header, sizeof(msg_header)); if (ret) { goto end; } - ret = lttng_notification_serialize(¬ification, &msg_buffer); + ret = lttng_notification_serialize(¬ification, &msg_payload); if (ret) { ERR("[notification-thread] Failed to serialize notification"); ret = -1; @@ -2865,24 +3826,60 @@ int send_evaluation_to_clients(const struct lttng_trigger *trigger, } /* Update payload size. */ - ((struct lttng_notification_channel_message * ) msg_buffer.data)->size = - (uint32_t) (msg_buffer.size - sizeof(msg_header)); + ((struct lttng_notification_channel_message *) msg_payload.buffer.data) + ->size = (uint32_t)( + msg_payload.buffer.size - sizeof(msg_header)); + + /* Update the payload number of fds. */ + { + const struct lttng_payload_view pv = lttng_payload_view_from_payload( + &msg_payload, 0, -1); + + ((struct lttng_notification_channel_message *) + msg_payload.buffer.data)->fds = (uint32_t) + lttng_payload_view_get_fd_handle_count(&pv); + } + pthread_mutex_lock(&client_list->lock); cds_list_for_each_entry_safe(client_list_element, tmp, &client_list->list, node) { + enum client_transmission_status transmission_status; struct notification_client *client = client_list_element->client; - if (client->uid != channel_uid && client->gid != channel_gid && - client->uid != 0) { - /* Client is not allowed to monitor this channel. */ - DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this channel"); - continue; + ret = 0; + pthread_mutex_lock(&client->lock); + if (!client->communication.active) { + /* + * Skip inactive client (protocol error or + * disconnecting). + */ + DBG("Skipping client at it is marked as inactive"); + goto skip_client; + } + + if (source_object_creds) { + if (client->uid != lttng_credentials_get_uid(source_object_creds) && + client->gid != lttng_credentials_get_gid(source_object_creds) && + client->uid != 0) { + /* + * Client is not allowed to monitor this + * object. + */ + DBG("[notification-thread] Skipping client at it does not have the object permission to receive notification for this trigger"); + goto skip_client; + } + } + + if (client->uid != lttng_credentials_get_uid(trigger_creds) && client->gid != lttng_credentials_get_gid(trigger_creds)) { + DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this trigger"); + goto skip_client; } DBG("[notification-thread] Sending notification to client (fd = %i, %zu bytes)", - client->socket, msg_buffer.size); - if (client->communication.outbound.buffer.size) { + client->socket, msg_payload.buffer.size); + + if (client_has_outbound_data_left(client)) { /* * Outgoing data is already buffered for this client; * drop the notification and enqueue a "dropped @@ -2890,34 +3887,42 @@ int send_evaluation_to_clients(const struct lttng_trigger *trigger, * notification since the socket spilled-over to the * queue. */ - DBG("[notification-thread] Dropping notification addressed to client (socket fd = %i)", - client->socket); - if (!client->communication.outbound.dropped_notification) { - client->communication.outbound.dropped_notification = true; - ret = client_enqueue_dropped_notification( - client, state); - if (ret) { - goto end; - } + ret = client_notification_overflow(client); + if (ret) { + /* Fatal error. */ + goto skip_client; } - continue; } - ret = lttng_dynamic_buffer_append_buffer( - &client->communication.outbound.buffer, - &msg_buffer); + ret = lttng_payload_copy(&msg_payload, &client->communication.outbound.payload); if (ret) { - goto end; + /* Fatal error. */ + goto skip_client; } - ret = client_flush_outgoing_queue(client, state); + transmission_status = client_flush_outgoing_queue(client); + pthread_mutex_unlock(&client->lock); + ret = client_report(client, transmission_status, user_data); if (ret) { - goto end; + /* Fatal error. */ + goto end_unlock_list; + } + + continue; + +skip_client: + pthread_mutex_unlock(&client->lock); + if (ret) { + /* Fatal error. */ + goto end_unlock_list; } } ret = 0; + +end_unlock_list: + pthread_mutex_unlock(&client_list->lock); end: - lttng_dynamic_buffer_reset(&msg_buffer); + lttng_payload_reset(&msg_payload); return ret; } @@ -2935,6 +3940,7 @@ int handle_notification_thread_channel_sample( bool previous_sample_available = false; struct channel_state_sample previous_sample, latest_sample; uint64_t previous_session_consumed_total, latest_session_consumed_total; + struct lttng_credentials channel_creds; /* * The monitoring pipe only holds messages smaller than PIPE_BUF, @@ -3053,70 +4059,90 @@ int handle_notification_thread_channel_sample( goto end_unlock; } + channel_creds = (typeof(channel_creds)) { + .uid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->gid), + }; + trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, channel_triggers_ht_node); cds_list_for_each_entry(trigger_list_element, &trigger_list->list, - node) { + node) { const struct lttng_condition *condition; - const struct lttng_action *action; - const struct lttng_trigger *trigger; - struct notification_client_list *client_list; + struct lttng_trigger *trigger; + struct notification_client_list *client_list = NULL; struct lttng_evaluation *evaluation = NULL; + enum action_executor_status executor_status; + ret = 0; trigger = trigger_list_element->trigger; condition = lttng_trigger_get_const_condition(trigger); assert(condition); - action = lttng_trigger_get_const_action(trigger); - - /* Notify actions are the only type currently supported. */ - assert(lttng_action_get_type_const(action) == - LTTNG_ACTION_TYPE_NOTIFY); /* * Check if any client is subscribed to the result of this * evaluation. */ - cds_lfht_lookup(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - match_client_list, - trigger, - &iter); - node = cds_lfht_iter_get_node(&iter); - assert(node); + client_list = get_client_list_from_condition(state, condition); - client_list = caa_container_of(node, - struct notification_client_list, - notification_trigger_ht_node); - if (cds_list_empty(&client_list->list)) { - /* - * No clients interested in the evaluation's result, - * skip it. - */ - continue; - } - - ret = evaluate_condition(condition, &evaluation, state, + ret = evaluate_buffer_condition(condition, &evaluation, state, previous_sample_available ? &previous_sample : NULL, &latest_sample, previous_session_consumed_total, latest_session_consumed_total, channel_info); if (caa_unlikely(ret)) { - goto end_unlock; + goto put_list; } if (caa_likely(!evaluation)) { - continue; + goto put_list; + } + + if (!lttng_trigger_should_fire(trigger)) { + goto put_list; + } + + lttng_trigger_fire(trigger); + + /* + * Ownership of `evaluation` transferred to the action executor + * no matter the result. + */ + executor_status = action_executor_enqueue(state->executor, + trigger, evaluation, &channel_creds, + client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated with buffer-condition trigger"); + ret = -1; + goto put_list; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated with buffer-condition trigger"); + ret = 0; + goto put_list; + default: + abort(); } - /* Dispatch evaluation result to all clients. */ - ret = send_evaluation_to_clients(trigger_list_element->trigger, - evaluation, client_list, state, - channel_info->session_info->uid, - channel_info->session_info->gid); - lttng_evaluation_destroy(evaluation); +put_list: + notification_client_list_put(client_list); if (caa_unlikely(ret)) { - goto end_unlock; + break; } } end_unlock: