X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Fbin%2Flttng-sessiond%2Fnotification-thread-events.c;h=86a114c07ddb0cafc9acd5533d1315bce10d7343;hp=3f8cb4804ddd0800061f634afb3d6e92022e8103;hb=882093eef6fdd658833928a62be5d42fc0cdcb00;hpb=0ab399e0bcf4e99492845f02e3e5b405155dea92 diff --git a/src/bin/lttng-sessiond/notification-thread-events.c b/src/bin/lttng-sessiond/notification-thread-events.c index 3f8cb4804..86a114c07 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ b/src/bin/lttng-sessiond/notification-thread-events.c @@ -5,6 +5,8 @@ * */ +#include "lttng/action/action.h" +#include "lttng/trigger/trigger-internal.h" #define _LGPL_SOURCE #include #include @@ -50,7 +52,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; }; @@ -117,122 +119,6 @@ struct lttng_condition_list_element { struct cds_list_head node; }; -struct notification_client_list_element { - struct notification_client *client; - struct cds_list_head node; -}; - -/* - * Thread safety of notification_client and notification_client_list. - * - * The notification thread (main thread) and the action executor - * interact through client lists. Hence, when the action executor - * thread looks-up the list of clients subscribed to a given - * condition, it will acquire a reference to the list and lock it - * while attempting to communicate with the various clients. - * - * It is not necessary to reference-count clients as they are guaranteed - * to be 'alive' if they are present in a list and that list is locked. Indeed, - * removing references to the client from those subscription lists is part of - * the work performed on destruction of a client. - * - * No provision for other access scenarios are taken into account; - * this is the bare minimum to make these accesses safe and the - * notification thread's state is _not_ "thread-safe" in any general - * sense. - */ -struct notification_client_list { - pthread_mutex_t lock; - struct urcu_ref ref; - const struct lttng_trigger *trigger; - struct cds_list_head list; - /* Weak reference to container. */ - struct cds_lfht *notification_trigger_clients_ht; - struct cds_lfht_node notification_trigger_clients_ht_node; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - -struct notification_client { - /* Nests within the notification_client_list lock. */ - pthread_mutex_t lock; - notification_client_id id; - 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 cds_lfht_node client_id_ht_node; - struct { - /* - * If a client's communication is inactive, it means that a - * fatal error has occurred (could be either a protocol error or - * the socket API returned a fatal error). No further - * communication should be attempted; the client is queued for - * clean-up. - */ - bool active; - 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; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - struct channel_state_sample { struct channel_key key; struct cds_lfht_node channel_state_ht_node; @@ -293,8 +179,13 @@ 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 int match_client_socket(struct cds_lfht_node *node, const void *key) @@ -722,7 +613,7 @@ error: return NULL; } -static +LTTNG_HIDDEN bool notification_client_list_get(struct notification_client_list *list) { return urcu_ref_get_unless_zero(&list->ref); @@ -785,6 +676,7 @@ void publish_notification_client_list( 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; @@ -796,7 +688,7 @@ void publish_notification_client_list( rcu_read_unlock(); } -static +LTTNG_HIDDEN void notification_client_list_put(struct notification_client_list *list) { if (!list) { @@ -1292,8 +1184,8 @@ void notification_client_destroy(struct notification_client *client, client->socket = -1; } client->communication.active = false; - lttng_dynamic_buffer_reset(&client->communication.inbound.buffer); - lttng_dynamic_buffer_reset(&client->communication.outbound.buffer); + 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); } @@ -1326,6 +1218,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, @@ -1529,7 +1449,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 = @@ -1886,6 +1806,10 @@ int handle_notification_thread_command_session_rotation( 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 = session_uid, + .gid = session_gid, + }; rcu_read_lock(); @@ -1911,12 +1835,11 @@ int handle_notification_thread_command_session_rotation( cds_list_for_each_entry(trigger_list_element, &trigger_list->list, node) { const struct lttng_condition *condition; - const struct lttng_action *action; - const struct lttng_trigger *trigger; + struct lttng_trigger *trigger; struct notification_client_list *client_list; struct lttng_evaluation *evaluation = NULL; enum lttng_condition_type condition_type; - bool client_list_is_empty; + enum action_executor_status executor_status; trigger = trigger_list_element->trigger; condition = lttng_trigger_get_const_condition(trigger); @@ -1931,26 +1854,7 @@ int handle_notification_thread_command_session_rotation( continue; } - 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); - client_list = get_client_list_from_condition(state, condition); - assert(client_list); - - pthread_mutex_lock(&client_list->lock); - client_list_is_empty = cds_list_empty(&client_list->list); - pthread_mutex_unlock(&client_list->lock); - if (client_list_is_empty) { - /* - * No clients interested in the evaluation's result, - * skip it. - */ - continue; - } - if (cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { evaluation = lttng_evaluation_session_rotation_ongoing_create( trace_archive_chunk_id); @@ -1966,12 +1870,40 @@ int handle_notification_thread_command_session_rotation( goto put_list; } - /* Dispatch evaluation result to all clients. */ - ret = send_evaluation_to_clients(trigger_list_element->trigger, - evaluation, client_list, state, - session_info->uid, - session_info->gid); - lttng_evaluation_destroy(evaluation); + /* + * 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)) { @@ -2025,7 +1957,7 @@ 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; @@ -2071,7 +2003,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; @@ -2115,9 +2047,46 @@ 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_const(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_const(inner_action); + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + is_notify = true; + goto end; + } + } + +end: + return is_notify; +} + /* - * 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, @@ -2142,10 +2111,13 @@ 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; bool free_trigger = true; + struct lttng_evaluation *evaluation = NULL; + struct lttng_credentials object_creds; + enum action_executor_status executor_status; rcu_read_lock(); @@ -2196,27 +2168,38 @@ int handle_notification_thread_command_register_trigger( * It is not skipped as this is the only action type currently * supported. */ - client_list = notification_client_list_create(trigger); - if (!client_list) { - ret = -1; - goto error_free_ht_element; - } - - /* 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_put_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); + + /* + * Client list ownership transferred to the + * notification_trigger_clients_ht. + */ + publish_notification_client_list(state, client_list); } switch (get_condition_binding_object(condition)) { @@ -2240,15 +2223,18 @@ int handle_notification_thread_command_register_trigger( 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_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 @@ -2270,24 +2256,72 @@ int handle_notification_thread_command_register_trigger( * current state. Otherwise, the next evaluation cycle may only see * 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. - * - * No need to lock the list here as it has not been published yet. */ - 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_put_client_list; - } + switch (get_condition_binding_object(condition)) { + case LTTNG_OBJECT_TYPE_SESSION: + ret = evaluate_session_condition_for_client(condition, state, + &evaluation, &object_creds.uid, + &object_creds.gid); + break; + case LTTNG_OBJECT_TYPE_CHANNEL: + ret = evaluate_channel_condition_for_client(condition, state, + &evaluation, &object_creds.uid, + &object_creds.gid); + break; + case LTTNG_OBJECT_TYPE_NONE: + ret = 0; + goto error_put_client_list; + case LTTNG_OBJECT_TYPE_UNKNOWN: + default: + ret = -1; + goto error_put_client_list; + } + + if (ret) { + /* Fatal error. */ + goto error_put_client_list; + } + + DBG("Newly registered trigger's condition evaluated to %s", + evaluation ? "true" : "false"); + if (!evaluation) { + /* Evaluation yielded nothing. Normal exit. */ + ret = 0; + goto error_put_client_list; } /* - * Client list ownership transferred to the - * notification_trigger_clients_ht. + * Ownership of `evaluation` transferred to the action executor + * no matter the result. */ - publish_notification_client_list(state, client_list); - 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 error_put_client_list; + default: + abort(); + } *cmd_result = LTTNG_OK; @@ -2412,6 +2446,8 @@ int handle_notification_thread_command( 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"); @@ -2465,6 +2501,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; @@ -2474,19 +2538,16 @@ int handle_notification_thread_command( goto error_unlock; } end: - cds_list_del(&cmd->cmd_list_node); if (cmd->is_async) { free(cmd); cmd = NULL; } else { lttng_waiter_wake_up(&cmd->reply_waiter); } - pthread_mutex_unlock(&handle->cmd_queue.lock); 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. */ @@ -2516,17 +2577,13 @@ end: return ret; } -/* Client lock must be acquired by caller. */ static int client_reset_inbound_state(struct notification_client *client) { int ret; - ASSERT_LOCKED(client->lock); - 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); @@ -2535,8 +2592,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; } @@ -2554,16 +2612,15 @@ 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; - pthread_mutex_lock(&client->lock); ret = client_reset_inbound_state(client); - pthread_mutex_unlock(&client->lock); if (ret) { ERR("[notification-thread] Failed to reset client communication's inbound state"); ret = 0; @@ -2613,13 +2670,16 @@ int handle_notification_thread_client_connect( rcu_read_unlock(); return ret; + error: notification_client_destroy(client, state); return ret; } -/* RCU read-lock must be held by the caller. */ -/* Client lock must be held by the caller */ +/* + * 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, @@ -2629,16 +2689,18 @@ int notification_thread_client_disconnect( 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); } - cds_lfht_del(state->client_socket_ht, &client->client_socket_ht_node); - cds_lfht_del(state->client_id_ht, &client->client_id_ht_node); - /* Release all conditions to which the client was subscribed. */ cds_list_for_each_entry_safe(condition_list_element, tmp, &client->condition_list, node) { @@ -2672,9 +2734,7 @@ int handle_notification_thread_client_disconnect( goto end; } - pthread_mutex_lock(&client->lock); ret = notification_thread_client_disconnect(client, state); - pthread_mutex_unlock(&client->lock); end: rcu_read_unlock(); return ret; @@ -2693,10 +2753,8 @@ int handle_notification_thread_client_disconnect_all( client_socket_ht_node) { int ret; - pthread_mutex_lock(&client->lock); ret = notification_thread_client_disconnect( client, state); - pthread_mutex_unlock(&client->lock); if (ret) { error_encoutered = true; } @@ -2741,8 +2799,6 @@ int client_handle_transmission_status( goto end; } - client->communication.outbound.queued_command_reply = false; - client->communication.outbound.dropped_notification = false; break; case CLIENT_TRANSMISSION_STATUS_QUEUED: /* @@ -2774,67 +2830,139 @@ end: /* Client lock must be acquired by caller. */ static enum client_transmission_status client_flush_outgoing_queue( - struct notification_client *client, - struct notification_thread_state *state) + 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 && 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, + memcpy(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) { - status = CLIENT_TRANSMISSION_STATUS_ERROR; goto error; } + status = CLIENT_TRANSMISSION_STATUS_QUEUED; + goto end; } else if (ret < 0) { - /* Generic error, disconnect the client. */ + /* Generic error, disable the client's communication. */ ERR("[notification-thread] Failed to flush outgoing queue, disconnecting client (socket fd = %i)", client->socket); + 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); + &client->communication.outbound.payload.buffer, 0); if (ret) { - status = CLIENT_TRANSMISSION_STATUS_ERROR; 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 = client_handle_transmission_status(client, status, state); - if (ret) { - goto error; + 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); } - return 0; +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 -1; + return CLIENT_TRANSMISSION_STATUS_ERROR; } -/* Client lock must be acquired by caller. */ +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, @@ -2849,37 +2977,43 @@ int client_send_command_reply(struct notification_client *client, .size = sizeof(reply), }; char buffer[sizeof(msg) + sizeof(reply)]; + enum client_transmission_status transmission_status; - ASSERT_LOCKED(client->lock); + 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; + goto error_unlock; } - memcpy(buffer, &msg, sizeof(msg)); - memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); - DBG("[notification-thread] Send command reply (%i)", (int) status); - /* Enqueue buffer to outgoing queue and flush it. */ ret = lttng_dynamic_buffer_append( - &client->communication.outbound.buffer, + &client->communication.outbound.payload.buffer, buffer, sizeof(buffer)); if (ret) { - goto error; + goto error_unlock; } - ret = client_flush_outgoing_queue(client, state); - if (ret) { - goto error; - } + transmission_status = client_flush_outgoing_queue(client); - if (client->communication.outbound.buffer.size != 0) { + 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; } @@ -2889,9 +3023,6 @@ int client_handle_message_unknown(struct notification_client *client, struct notification_thread_state *state) { int ret; - - pthread_mutex_lock(&client->lock); - /* * Receiving message header. The function will be called again * once the rest of the message as been received and can be @@ -2899,9 +3030,9 @@ int client_handle_message_unknown(struct notification_client *client, */ const struct lttng_notification_channel_message *msg; - assert(sizeof(*msg) == client->communication.inbound.buffer.size); + assert(sizeof(*msg) == client->communication.inbound.payload.buffer.size); msg = (const struct lttng_notification_channel_message *) - client->communication.inbound.buffer.data; + client->communication.inbound.payload.buffer.data; if (msg->size == 0 || msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { @@ -2923,12 +3054,15 @@ int client_handle_message_unknown(struct notification_client *client, } 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.buffer, msg->size); + &client->communication.inbound.payload.buffer, msg->size); + + /* msg is not valid anymore due to lttng_dynamic_buffer_set_size. */ + msg = NULL; end: - pthread_mutex_unlock(&client->lock); return ret; } @@ -2950,15 +3084,13 @@ int client_handle_message_handshake(struct notification_client *client, LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; char send_buffer[sizeof(msg_header) + sizeof(handshake_reply)]; - pthread_mutex_lock(&client->lock); - 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 + client->communication.inbound.payload.buffer .data; client->major = handshake_client->major; client->minor = handshake_client->minor; @@ -2981,37 +3113,38 @@ int client_handle_message_handshake(struct notification_client *client, status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; } + pthread_mutex_lock(&client->lock); + /* Outgoing queue will be flushed when the command reply is sent. */ ret = lttng_dynamic_buffer_append( - &client->communication.outbound.buffer, send_buffer, + &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; + goto end_unlock; } client->validated = true; client->communication.active = true; + pthread_mutex_unlock(&client->lock); - ret = client_flush_outgoing_queue(client, state); + /* 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; } - /* 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; - } - -end: + goto end; +end_unlock: pthread_mutex_unlock(&client->lock); +end: return ret; } @@ -3026,15 +3159,17 @@ int client_handle_message_subscription( enum lttng_notification_channel_status status = LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; struct lttng_payload_view condition_view = - lttng_payload_view_from_dynamic_buffer( - &client->communication.inbound.buffer, + lttng_payload_view_from_payload( + &client->communication.inbound.payload, 0, -1); size_t expected_condition_size; - pthread_mutex_lock(&client->lock); - expected_condition_size = client->communication.inbound.buffer.size; - pthread_mutex_unlock(&client->lock); - + /* + * 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"); @@ -3048,26 +3183,24 @@ int client_handle_message_subscription( ret = notification_thread_client_unsubscribe( client, condition, state, &status); } - if (ret) { - goto end; - } - pthread_mutex_lock(&client->lock); - ret = client_send_command_reply(client, state, status); if (ret) { - ERR("[notification-thread] Failed to send reply to notification channel client"); - goto end_unlock; + 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_unlock; + 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; } -end_unlock: - pthread_mutex_unlock(&client->lock); end: return ret; } @@ -3121,8 +3254,8 @@ int handle_notification_thread_client_in( struct notification_client *client; ssize_t recv_ret; size_t offset; - bool message_is_complete = false; + rcu_read_lock(); client = get_client_from_socket(socket, state); if (!client) { /* Internal error, abort. */ @@ -3130,12 +3263,11 @@ int handle_notification_thread_client_in( goto end; } - pthread_mutex_lock(&client->lock); - 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) { @@ -3144,36 +3276,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) { client->communication.inbound.bytes_to_receive -= recv_ret; - message_is_complete = client->communication.inbound - .bytes_to_receive == 0; - } - pthread_mutex_unlock(&client->lock); - if (recv_ret < 0) { + } else { goto error_disconnect_client; } - if (message_is_complete) { - 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; } } + + /* 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: - pthread_mutex_lock(&client->lock); ret = notification_thread_client_disconnect(client, state); - pthread_mutex_unlock(&client->lock); - return ret; + goto end; } /* Client ready to receive outgoing data. */ @@ -3182,7 +3347,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. */ @@ -3191,12 +3358,16 @@ int handle_notification_thread_client_out( } pthread_mutex_lock(&client->lock); - ret = client_flush_outgoing_queue(client, state); + 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; } @@ -3363,22 +3534,62 @@ end: } static -int client_enqueue_dropped_notification(struct notification_client *client) +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 object_uid, gid_t object_gid) +{ + return notification_client_list_send_evaluation(client_list, + lttng_trigger_get_const_condition(trigger), evaluation, + lttng_trigger_get_credentials(trigger), + &(struct lttng_credentials){ + .uid = object_uid, .gid = object_gid}, + 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 @@ -3412,24 +3623,26 @@ int client_enqueue_dropped_notification(struct notification_client *client) * interference from external users (those could, for instance, unregister * their triggers). */ -static -int send_evaluation_to_clients(const struct lttng_trigger *trigger, +LTTNG_HIDDEN +int notification_client_list_send_evaluation( + struct notification_client_list *client_list, + const struct lttng_condition *condition, const struct lttng_evaluation *evaluation, - struct notification_client_list* client_list, - struct notification_thread_state *state, - uid_t object_uid, gid_t object_gid) + 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_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, }; - const struct lttng_credentials *trigger_creds = lttng_trigger_get_credentials(trigger); lttng_payload_init(&msg_payload); @@ -3451,29 +3664,56 @@ int send_evaluation_to_clients(const struct lttng_trigger *trigger, ->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; ret = 0; pthread_mutex_lock(&client->lock); - if (client->uid != object_uid && client->gid != object_gid && - client->uid != 0) { - /* Client is not allowed to monitor this channel. */ - DBG("[notification-thread] Skipping client at it does not have the object permission to receive notification for this trigger"); - goto unlock_client; + 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 != source_object_creds->uid && + client->gid != source_object_creds->gid && + 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 != trigger_creds->uid && client->gid != trigger_creds->gid) { DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this trigger"); - goto unlock_client; + goto skip_client; } DBG("[notification-thread] Sending notification to client (fd = %i, %zu bytes)", client->socket, msg_payload.buffer.size); - if (client->communication.outbound.buffer.size) { + + if (client_has_outbound_data_left(client)) { /* * Outgoing data is already buffered for this client; * drop the notification and enqueue a "dropped @@ -3481,33 +3721,33 @@ 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); - if (ret) { - goto unlock_client; - } + ret = client_notification_overflow(client); + if (ret) { + /* Fatal error. */ + goto skip_client; } - goto unlock_client; } - ret = lttng_dynamic_buffer_append_buffer( - &client->communication.outbound.buffer, - &msg_payload.buffer); + ret = lttng_payload_copy(&msg_payload, &client->communication.outbound.payload); if (ret) { - goto unlock_client; + /* 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 unlock_client; + /* Fatal error. */ + goto end_unlock_list; } -unlock_client: + + continue; + +skip_client: pthread_mutex_unlock(&client->lock); if (ret) { + /* Fatal error. */ goto end_unlock_list; } } @@ -3534,6 +3774,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, @@ -3652,41 +3893,31 @@ int handle_notification_thread_channel_sample( goto end_unlock; } + channel_creds = (typeof(channel_creds)) { + .uid = channel_info->session_info->uid, + .gid = 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) { const struct lttng_condition *condition; - const struct lttng_action *action; - const struct lttng_trigger *trigger; + struct lttng_trigger *trigger; struct notification_client_list *client_list = NULL; struct lttng_evaluation *evaluation = NULL; - bool client_list_is_empty; + 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. */ client_list = get_client_list_from_condition(state, condition); - assert(client_list); - client_list_is_empty = cds_list_empty(&client_list->list); - if (client_list_is_empty) { - /* - * No clients interested in the evaluation's result, - * skip it. - */ - goto put_list; - } ret = evaluate_buffer_condition(condition, &evaluation, state, previous_sample_available ? &previous_sample : NULL, @@ -3702,12 +3933,40 @@ int handle_notification_thread_channel_sample( goto put_list; } - /* 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); + /* + * 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(); + } + put_list: notification_client_list_put(client_list); if (caa_unlikely(ret)) {