+ /*
+ * A reference to the trigger is acquired by the notification thread.
+ * It is safe to return the same trigger to the caller since it the
+ * other user holds a reference.
+ *
+ * The trigger is modified during the execution of the
+ * "register trigger" command. However, by the time the command returns,
+ * it is safe to use without any locking as its properties are
+ * immutable.
+ */
+ ret_code = notification_thread_command_register_trigger(
+ notification_thread, trigger, is_trigger_anonymous);
+ if (ret_code != LTTNG_OK) {
+ DBG("Failed to register trigger to notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d",
+ trigger_name, (int) trigger_owner, ret_code);
+ goto end;
+ }
+
+ trigger_status = lttng_trigger_get_name(trigger, &trigger_name);
+ trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ?
+ trigger_name : "(anonymous)";
+
+ /*
+ * Synchronize tracers if the trigger adds an event notifier.
+ */
+ if (lttng_trigger_needs_tracer_notifier(trigger)) {
+ ret_code = synchronize_tracer_notifier_register(notification_thread,
+ trigger, cmd_creds);
+ if (ret_code != LTTNG_OK) {
+ ERR("Error registering tracer notifier: %s",
+ lttng_strerror(-ret_code));
+ goto end;
+ }
+ }
+
+ /*
+ * Return an updated trigger to the client.
+ *
+ * Since a modified version of the same trigger is returned, acquire a
+ * reference to the trigger so the caller doesn't have to care if those
+ * are distinct instances or not.
+ */
+ if (ret_code == LTTNG_OK) {
+ lttng_trigger_get(trigger);
+ *return_trigger = trigger;
+ /* Ownership of trigger was transferred to caller. */
+ trigger = NULL;
+ }
+end:
+ return ret_code;
+}
+
+static
+enum lttng_error_code synchronize_tracer_notifier_unregister(
+ const struct lttng_trigger *trigger)
+{
+ enum lttng_error_code ret_code;
+ const struct lttng_condition *condition =
+ lttng_trigger_get_const_condition(trigger);
+ const enum lttng_domain_type trigger_domain =
+ lttng_trigger_get_underlying_domain_type_restriction(
+ trigger);
+
+ LTTNG_ASSERT(condition);
+ LTTNG_ASSERT(lttng_condition_get_type(condition) ==
+ LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES);
+
+ session_lock_list();
+ switch (trigger_domain) {
+ case LTTNG_DOMAIN_KERNEL:
+ ret_code = kernel_unregister_event_notifier(trigger);
+ if (ret_code != LTTNG_OK) {
+ goto end_unlock_session_list;
+ }
+
+ break;
+ case LTTNG_DOMAIN_UST:
+ ust_app_global_update_all_event_notifier_rules();
+ break;
+ case LTTNG_DOMAIN_JUL:
+ case LTTNG_DOMAIN_LOG4J:
+ case LTTNG_DOMAIN_PYTHON:
+ {
+ /* Agent domains. */
+ struct agent *agt = agent_find_by_event_notifier_domain(
+ trigger_domain);
+
+ /*
+ * This trigger was never registered in the first place. Calling
+ * this function under those circumstances is an internal error.
+ */
+ LTTNG_ASSERT(agt);
+ ret_code = trigger_agent_disable(trigger, agt);
+ if (ret_code != LTTNG_OK) {
+ goto end_unlock_session_list;
+ }
+
+ break;
+ }
+ case LTTNG_DOMAIN_NONE:
+ default:
+ abort();
+ }
+
+ ret_code = LTTNG_OK;
+
+end_unlock_session_list:
+ session_unlock_list();
+ return ret_code;
+}
+
+enum lttng_error_code cmd_unregister_trigger(const struct lttng_credentials *cmd_creds,
+ const struct lttng_trigger *trigger,
+ struct notification_thread_handle *notification_thread)
+{
+ enum lttng_error_code ret_code;
+ const char *trigger_name;
+ uid_t trigger_owner;
+ enum lttng_trigger_status trigger_status;
+ struct lttng_trigger *sessiond_trigger = NULL;
+
+ trigger_status = lttng_trigger_get_name(trigger, &trigger_name);
+ trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? trigger_name : "(anonymous)";
+ trigger_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner);
+ LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK);
+
+ DBG("Running unregister trigger command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d",
+ trigger_name, (int) trigger_owner,
+ (int) lttng_credentials_get_uid(cmd_creds));
+
+ /*
+ * Validate the trigger credentials against the command credentials.
+ * Only the root user can unregister a trigger with non-matching
+ * credentials.
+ */
+ if (!lttng_credentials_is_equal_uid(
+ lttng_trigger_get_credentials(trigger),
+ cmd_creds)) {
+ if (lttng_credentials_get_uid(cmd_creds) != 0) {
+ ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d",
+ trigger_name, (int) trigger_owner,
+ (int) lttng_credentials_get_uid(cmd_creds));
+ ret_code = LTTNG_ERR_INVALID_TRIGGER;
+ goto end;
+ }
+ }
+
+ /* Fetch the sessiond side trigger object. */
+ ret_code = notification_thread_command_get_trigger(
+ notification_thread, trigger, &sessiond_trigger);
+ if (ret_code != LTTNG_OK) {
+ DBG("Failed to get trigger from notification thread during unregister: trigger name = '%s', trigger owner uid = %d, error code = %d",
+ trigger_name, (int) trigger_owner, ret_code);
+ goto end;
+ }
+
+ LTTNG_ASSERT(sessiond_trigger);
+
+ /*
+ * From this point on, no matter what, consider the trigger
+ * unregistered.
+ *
+ * We set the unregistered state of the sessiond side trigger object in
+ * the client thread since we want to minimize the possibility of the
+ * notification thread being stalled due to a long execution of an
+ * action that required the trigger lock.
+ */
+ lttng_trigger_set_as_unregistered(sessiond_trigger);
+
+ ret_code = notification_thread_command_unregister_trigger(notification_thread,
+ trigger);
+ if (ret_code != LTTNG_OK) {
+ DBG("Failed to unregister trigger from notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d",
+ trigger_name, (int) trigger_owner, ret_code);
+ goto end;
+ }
+
+ /*
+ * Synchronize tracers if the trigger removes an event notifier.
+ * Do this even if the trigger unregistration failed to at least stop
+ * the tracers from producing notifications associated with this
+ * event notifier.
+ */
+ if (lttng_trigger_needs_tracer_notifier(trigger)) {
+ ret_code = synchronize_tracer_notifier_unregister(trigger);
+ if (ret_code != LTTNG_OK) {
+ ERR("Error unregistering trigger to tracer.");
+ goto end;
+ }
+
+ }
+
+end:
+ lttng_trigger_put(sessiond_trigger);
+ return ret_code;
+}
+
+enum lttng_error_code cmd_list_triggers(struct command_ctx *cmd_ctx,
+ struct notification_thread_handle *notification_thread,
+ struct lttng_triggers **return_triggers)
+{
+ int ret;
+ enum lttng_error_code ret_code;
+ struct lttng_triggers *triggers = NULL;
+
+ /* Get the set of triggers from the notification thread. */
+ ret_code = notification_thread_command_list_triggers(
+ notification_thread, cmd_ctx->creds.uid, &triggers);
+ if (ret_code != LTTNG_OK) {
+ goto end;
+ }
+
+ ret = lttng_triggers_remove_hidden_triggers(triggers);
+ if (ret) {
+ ret_code = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ *return_triggers = triggers;
+ triggers = NULL;
+ ret_code = LTTNG_OK;
+end:
+ lttng_triggers_destroy(triggers);
+ return ret_code;
+}
+
+enum lttng_error_code cmd_execute_error_query(const struct lttng_credentials *cmd_creds,
+ const struct lttng_error_query *query,
+ struct lttng_error_query_results **_results,
+ struct notification_thread_handle *notification_thread)
+{
+ enum lttng_error_code ret_code;
+ const struct lttng_trigger *query_target_trigger;
+ const struct lttng_action *query_target_action = NULL;
+ struct lttng_trigger *matching_trigger = NULL;
+ const char *trigger_name;
+ uid_t trigger_owner;
+ enum lttng_trigger_status trigger_status;
+ struct lttng_error_query_results *results = NULL;
+
+ switch (lttng_error_query_get_target_type(query)) {
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER:
+ query_target_trigger = lttng_error_query_trigger_borrow_target(query);
+ break;
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION:
+ query_target_trigger =
+ lttng_error_query_condition_borrow_target(query);
+ break;
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION:
+ query_target_trigger = lttng_error_query_action_borrow_trigger_target(
+ query);
+ break;
+ default:
+ abort();
+ }
+
+ LTTNG_ASSERT(query_target_trigger);
+
+ ret_code = notification_thread_command_get_trigger(notification_thread,
+ query_target_trigger, &matching_trigger);
+ if (ret_code != LTTNG_OK) {
+ goto end;
+ }
+
+ /* No longer needed. */
+ query_target_trigger = NULL;
+
+ if (lttng_error_query_get_target_type(query) ==
+ LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION) {
+ /* Get the sessiond-side version of the target action. */
+ query_target_action =
+ lttng_error_query_action_borrow_action_target(
+ query, matching_trigger);
+ }
+
+ trigger_status = lttng_trigger_get_name(matching_trigger, &trigger_name);
+ trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ?
+ trigger_name : "(anonymous)";
+ trigger_status = lttng_trigger_get_owner_uid(matching_trigger,
+ &trigger_owner);
+ LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK);
+
+ results = lttng_error_query_results_create();
+ if (!results) {
+ ret_code = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+
+ DBG("Running \"execute error query\" command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d",
+ trigger_name, (int) trigger_owner,
+ (int) lttng_credentials_get_uid(cmd_creds));
+
+ /*
+ * Validate the trigger credentials against the command credentials.
+ * Only the root user can target a trigger with non-matching
+ * credentials.
+ */
+ if (!lttng_credentials_is_equal_uid(
+ lttng_trigger_get_credentials(matching_trigger),
+ cmd_creds)) {
+ if (lttng_credentials_get_uid(cmd_creds) != 0) {
+ ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d",
+ trigger_name, (int) trigger_owner,
+ (int) lttng_credentials_get_uid(cmd_creds));
+ ret_code = LTTNG_ERR_INVALID_TRIGGER;
+ goto end;
+ }
+ }
+
+ switch (lttng_error_query_get_target_type(query)) {
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER:
+ trigger_status = lttng_trigger_add_error_results(
+ matching_trigger, results);
+
+ switch (trigger_status) {
+ case LTTNG_TRIGGER_STATUS_OK:
+ break;
+ default:
+ ret_code = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ break;
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION:
+ {
+ trigger_status = lttng_trigger_condition_add_error_results(
+ matching_trigger, results);
+
+ switch (trigger_status) {
+ case LTTNG_TRIGGER_STATUS_OK:
+ break;
+ default:
+ ret_code = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ break;
+ }
+ case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION:
+ {
+ const enum lttng_action_status action_status =
+ lttng_action_add_error_query_results(
+ query_target_action, results);
+
+ switch (action_status) {
+ case LTTNG_ACTION_STATUS_OK:
+ break;
+ default:
+ ret_code = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ break;
+ }
+ default:
+ abort();
+ break;
+ }
+
+ *_results = results;
+ results = NULL;
+ ret_code = LTTNG_OK;
+end:
+ lttng_trigger_put(matching_trigger);
+ lttng_error_query_results_destroy(results);
+ return ret_code;
+}
+
+/*
+ * Send relayd sockets from snapshot output to consumer. Ignore request if the
+ * snapshot output is *not* set with a remote destination.
+ *
+ * Return LTTNG_OK on success or a LTTNG_ERR code.
+ */
+static enum lttng_error_code set_relayd_for_snapshot(
+ struct consumer_output *output,
+ const struct ltt_session *session)
+{
+ enum lttng_error_code status = LTTNG_OK;
+ struct lttng_ht_iter iter;
+ struct consumer_socket *socket;
+ LTTNG_OPTIONAL(uint64_t) current_chunk_id = {};
+ const char *base_path;
+
+ LTTNG_ASSERT(output);
+ LTTNG_ASSERT(session);
+
+ DBG2("Set relayd object from snapshot output");
+
+ if (session->current_trace_chunk) {
+ enum lttng_trace_chunk_status chunk_status =
+ lttng_trace_chunk_get_id(
+ session->current_trace_chunk,
+ ¤t_chunk_id.value);
+
+ if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK) {
+ current_chunk_id.is_set = true;
+ } else {
+ ERR("Failed to get current trace chunk id");
+ status = LTTNG_ERR_UNK;
+ goto error;
+ }
+ }
+
+ /* Ignore if snapshot consumer output is not network. */
+ if (output->type != CONSUMER_DST_NET) {
+ goto error;
+ }
+
+ /*
+ * The snapshot record URI base path overrides the session
+ * base path.
+ */
+ if (output->dst.net.control.subdir[0] != '\0') {
+ base_path = output->dst.net.control.subdir;
+ } else {
+ base_path = session->base_path;
+ }
+
+ /*
+ * For each consumer socket, create and send the relayd object of the
+ * snapshot output.
+ */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(output->socks->ht, &iter.iter,
+ socket, node.node) {
+ pthread_mutex_lock(socket->lock);
+ status = send_consumer_relayd_sockets(0, session->id,
+ output, socket,
+ session->name, session->hostname,
+ base_path,
+ session->live_timer,
+ current_chunk_id.is_set ? ¤t_chunk_id.value : NULL,
+ session->creation_time,
+ session->name_contains_creation_time);
+ pthread_mutex_unlock(socket->lock);
+ if (status != LTTNG_OK) {
+ rcu_read_unlock();
+ goto error;
+ }
+ }
+ rcu_read_unlock();
+
+error:
+ return status;
+}
+
+/*
+ * Record a kernel snapshot.
+ *
+ * Return LTTNG_OK on success or a LTTNG_ERR code.
+ */
+static enum lttng_error_code record_kernel_snapshot(
+ struct ltt_kernel_session *ksess,
+ const struct consumer_output *output,
+ const struct ltt_session *session,
+ int wait, uint64_t nb_packets_per_stream)
+{
+ enum lttng_error_code status;
+
+ LTTNG_ASSERT(ksess);
+ LTTNG_ASSERT(output);
+ LTTNG_ASSERT(session);
+
+ status = kernel_snapshot_record(
+ ksess, output, wait, nb_packets_per_stream);
+ return status;
+}
+
+/*
+ * Record a UST snapshot.
+ *
+ * Returns LTTNG_OK on success or a LTTNG_ERR error code.
+ */
+static enum lttng_error_code record_ust_snapshot(struct ltt_ust_session *usess,
+ const struct consumer_output *output,
+ const struct ltt_session *session,
+ int wait, uint64_t nb_packets_per_stream)
+{
+ enum lttng_error_code status;
+
+ LTTNG_ASSERT(usess);
+ LTTNG_ASSERT(output);
+ LTTNG_ASSERT(session);
+
+ status = ust_app_snapshot_record(
+ usess, output, wait, nb_packets_per_stream);
+ return status;
+}
+
+static
+uint64_t get_session_size_one_more_packet_per_stream(
+ const struct ltt_session *session, uint64_t cur_nr_packets)
+{
+ uint64_t tot_size = 0;
+
+ if (session->kernel_session) {
+ struct ltt_kernel_channel *chan;
+ const struct ltt_kernel_session *ksess =
+ session->kernel_session;
+
+ cds_list_for_each_entry(chan, &ksess->channel_list.head, list) {
+ if (cur_nr_packets >= chan->channel->attr.num_subbuf) {
+ /*
+ * Don't take channel into account if we
+ * already grab all its packets.
+ */
+ continue;
+ }
+ tot_size += chan->channel->attr.subbuf_size
+ * chan->stream_count;
+ }
+ }
+
+ if (session->ust_session) {
+ const struct ltt_ust_session *usess = session->ust_session;
+
+ tot_size += ust_app_get_size_one_more_packet_per_stream(usess,
+ cur_nr_packets);
+ }
+
+ return tot_size;
+}
+
+/*
+ * Calculate the number of packets we can grab from each stream that
+ * fits within the overall snapshot max size.
+ *
+ * Returns -1 on error, 0 means infinite number of packets, else > 0 is
+ * the number of packets per stream.
+ *
+ * TODO: this approach is not perfect: we consider the worse case
+ * (packet filling the sub-buffers) as an upper bound, but we could do
+ * better if we do this calculation while we actually grab the packet
+ * content: we would know how much padding we don't actually store into
+ * the file.
+ *
+ * This algorithm is currently bounded by the number of packets per
+ * stream.
+ *
+ * Since we call this algorithm before actually grabbing the data, it's
+ * an approximation: for instance, applications could appear/disappear
+ * in between this call and actually grabbing data.
+ */
+static
+int64_t get_session_nb_packets_per_stream(const struct ltt_session *session,
+ uint64_t max_size)
+{
+ int64_t size_left;
+ uint64_t cur_nb_packets = 0;
+
+ if (!max_size) {
+ return 0; /* Infinite */
+ }
+
+ size_left = max_size;
+ for (;;) {
+ uint64_t one_more_packet_tot_size;
+
+ one_more_packet_tot_size = get_session_size_one_more_packet_per_stream(
+ session, cur_nb_packets);
+ if (!one_more_packet_tot_size) {
+ /* We are already grabbing all packets. */
+ break;
+ }
+ size_left -= one_more_packet_tot_size;
+ if (size_left < 0) {
+ break;
+ }
+ cur_nb_packets++;
+ }
+ if (!cur_nb_packets && size_left != max_size) {
+ /* Not enough room to grab one packet of each stream, error. */
+ return -1;
+ }
+ return cur_nb_packets;
+}
+
+static
+enum lttng_error_code snapshot_record(struct ltt_session *session,
+ const struct snapshot_output *snapshot_output, int wait)
+{
+ int64_t nb_packets_per_stream;
+ char snapshot_chunk_name[LTTNG_NAME_MAX];
+ int ret;
+ enum lttng_error_code ret_code = LTTNG_OK;
+ struct lttng_trace_chunk *snapshot_trace_chunk;
+ struct consumer_output *original_ust_consumer_output = NULL;
+ struct consumer_output *original_kernel_consumer_output = NULL;
+ struct consumer_output *snapshot_ust_consumer_output = NULL;
+ struct consumer_output *snapshot_kernel_consumer_output = NULL;
+
+ ret = snprintf(snapshot_chunk_name, sizeof(snapshot_chunk_name),
+ "%s-%s-%" PRIu64,
+ snapshot_output->name,
+ snapshot_output->datetime,
+ snapshot_output->nb_snapshot);
+ if (ret < 0 || ret >= sizeof(snapshot_chunk_name)) {
+ ERR("Failed to format snapshot name");
+ ret_code = LTTNG_ERR_INVALID;
+ goto error;
+ }
+ DBG("Recording snapshot \"%s\" for session \"%s\" with chunk name \"%s\"",
+ snapshot_output->name, session->name,
+ snapshot_chunk_name);
+ if (!session->kernel_session && !session->ust_session) {
+ ERR("Failed to record snapshot as no channels exist");
+ ret_code = LTTNG_ERR_NO_CHANNEL;
+ goto error;
+ }
+
+ if (session->kernel_session) {
+ original_kernel_consumer_output =
+ session->kernel_session->consumer;
+ snapshot_kernel_consumer_output =
+ consumer_copy_output(snapshot_output->consumer);
+ strcpy(snapshot_kernel_consumer_output->chunk_path,
+ snapshot_chunk_name);
+
+ /* Copy the original domain subdir. */
+ strcpy(snapshot_kernel_consumer_output->domain_subdir,
+ original_kernel_consumer_output->domain_subdir);
+
+ ret = consumer_copy_sockets(snapshot_kernel_consumer_output,
+ original_kernel_consumer_output);
+ if (ret < 0) {
+ ERR("Failed to copy consumer sockets from snapshot output configuration");
+ ret_code = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+ ret_code = set_relayd_for_snapshot(
+ snapshot_kernel_consumer_output, session);
+ if (ret_code != LTTNG_OK) {
+ ERR("Failed to setup relay daemon for kernel tracer snapshot");
+ goto error;
+ }
+ session->kernel_session->consumer =
+ snapshot_kernel_consumer_output;
+ }
+ if (session->ust_session) {
+ original_ust_consumer_output = session->ust_session->consumer;
+ snapshot_ust_consumer_output =
+ consumer_copy_output(snapshot_output->consumer);
+ strcpy(snapshot_ust_consumer_output->chunk_path,
+ snapshot_chunk_name);
+
+ /* Copy the original domain subdir. */
+ strcpy(snapshot_ust_consumer_output->domain_subdir,
+ original_ust_consumer_output->domain_subdir);
+
+ ret = consumer_copy_sockets(snapshot_ust_consumer_output,
+ original_ust_consumer_output);
+ if (ret < 0) {
+ ERR("Failed to copy consumer sockets from snapshot output configuration");
+ ret_code = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+ ret_code = set_relayd_for_snapshot(
+ snapshot_ust_consumer_output, session);
+ if (ret_code != LTTNG_OK) {
+ ERR("Failed to setup relay daemon for userspace tracer snapshot");
+ goto error;
+ }
+ session->ust_session->consumer =
+ snapshot_ust_consumer_output;
+ }
+
+ snapshot_trace_chunk = session_create_new_trace_chunk(session,
+ snapshot_kernel_consumer_output ?:
+ snapshot_ust_consumer_output,
+ consumer_output_get_base_path(
+ snapshot_output->consumer),
+ snapshot_chunk_name);
+ if (!snapshot_trace_chunk) {
+ ERR("Failed to create temporary trace chunk to record a snapshot of session \"%s\"",
+ session->name);
+ ret_code = LTTNG_ERR_CREATE_DIR_FAIL;
+ goto error;
+ }
+ LTTNG_ASSERT(!session->current_trace_chunk);
+ ret = session_set_trace_chunk(session, snapshot_trace_chunk, NULL);
+ lttng_trace_chunk_put(snapshot_trace_chunk);
+ snapshot_trace_chunk = NULL;
+ if (ret) {
+ ERR("Failed to set temporary trace chunk to record a snapshot of session \"%s\"",
+ session->name);
+ ret_code = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER;
+ goto error;
+ }
+
+ nb_packets_per_stream = get_session_nb_packets_per_stream(session,
+ snapshot_output->max_size);
+ if (nb_packets_per_stream < 0) {
+ ret_code = LTTNG_ERR_MAX_SIZE_INVALID;
+ goto error_close_trace_chunk;
+ }
+
+ if (session->kernel_session) {
+ ret_code = record_kernel_snapshot(session->kernel_session,
+ snapshot_kernel_consumer_output, session,
+ wait, nb_packets_per_stream);
+ if (ret_code != LTTNG_OK) {
+ goto error_close_trace_chunk;
+ }
+ }
+
+ if (session->ust_session) {
+ ret_code = record_ust_snapshot(session->ust_session,
+ snapshot_ust_consumer_output, session,
+ wait, nb_packets_per_stream);
+ if (ret_code != LTTNG_OK) {
+ goto error_close_trace_chunk;
+ }
+ }
+
+error_close_trace_chunk:
+ if (session_set_trace_chunk(session, NULL, &snapshot_trace_chunk)) {
+ ERR("Failed to release the current trace chunk of session \"%s\"",
+ session->name);
+ ret_code = LTTNG_ERR_UNK;
+ }
+
+ if (session_close_trace_chunk(session, snapshot_trace_chunk,
+ LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION, NULL)) {
+ /*
+ * Don't goto end; make sure the chunk is closed for the session
+ * to allow future snapshots.
+ */
+ ERR("Failed to close snapshot trace chunk of session \"%s\"",
+ session->name);
+ ret_code = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER;
+ }
+error:
+ if (original_ust_consumer_output) {
+ session->ust_session->consumer = original_ust_consumer_output;
+ }
+ if (original_kernel_consumer_output) {
+ session->kernel_session->consumer =
+ original_kernel_consumer_output;
+ }
+ consumer_output_put(snapshot_ust_consumer_output);
+ consumer_output_put(snapshot_kernel_consumer_output);
+ return ret_code;
+}
+
+/*
+ * Command LTTNG_SNAPSHOT_RECORD from lib lttng ctl.
+ *
+ * The wait parameter is ignored so this call always wait for the snapshot to
+ * complete before returning.
+ *
+ * Return LTTNG_OK on success or else a LTTNG_ERR code.
+ */
+int cmd_snapshot_record(struct ltt_session *session,
+ const struct lttng_snapshot_output *output, int wait)
+{
+ enum lttng_error_code cmd_ret = LTTNG_OK;
+ int ret;
+ unsigned int snapshot_success = 0;
+ char datetime[16];
+ struct snapshot_output *tmp_output = NULL;
+
+ LTTNG_ASSERT(session);
+ LTTNG_ASSERT(output);
+
+ DBG("Cmd snapshot record for session %s", session->name);
+
+ /* Get the datetime for the snapshot output directory. */
+ ret = utils_get_current_time_str("%Y%m%d-%H%M%S", datetime,
+ sizeof(datetime));
+ if (!ret) {
+ cmd_ret = LTTNG_ERR_INVALID;
+ goto error;
+ }
+
+ /*
+ * Permission denied to create an output if the session is not
+ * set in no output mode.
+ */
+ if (session->output_traces) {
+ cmd_ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION;
+ goto error;
+ }
+
+ /* The session needs to be started at least once. */
+ if (!session->has_been_started) {
+ cmd_ret = LTTNG_ERR_START_SESSION_ONCE;
+ goto error;
+ }
+
+ /* Use temporary output for the session. */
+ if (*output->ctrl_url != '\0') {
+ tmp_output = snapshot_output_alloc();
+ if (!tmp_output) {
+ cmd_ret = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ ret = snapshot_output_init(session, output->max_size,
+ output->name,
+ output->ctrl_url, output->data_url,
+ session->consumer,
+ tmp_output, NULL);
+ if (ret < 0) {
+ if (ret == -ENOMEM) {
+ cmd_ret = LTTNG_ERR_NOMEM;