+/*
+ * Command LTTNG_SNAPSHOT_ADD_OUTPUT from the lttng ctl library.
+ *
+ * Return LTTNG_OK on success or else a LTTNG_ERR code.
+ */
+int cmd_snapshot_add_output(struct ltt_session *session,
+ struct lttng_snapshot_output *output, uint32_t *id)
+{
+ int ret;
+ struct snapshot_output *new_output;
+
+ assert(session);
+ assert(output);
+
+ DBG("Cmd snapshot add output for session %s", session->name);
+
+ /*
+ * Permission denied to create an output if the session is not
+ * set in no output mode.
+ */
+ if (session->output_traces) {
+ ret = LTTNG_ERR_EPERM;
+ goto error;
+ }
+
+ /* Only one output is allowed until we have the "tee" feature. */
+ if (session->snapshot.nb_output == 1) {
+ ret = LTTNG_ERR_SNAPSHOT_OUTPUT_EXIST;
+ goto error;
+ }
+
+ new_output = snapshot_output_alloc();
+ if (!new_output) {
+ ret = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ ret = snapshot_output_init(output->max_size, output->name,
+ output->ctrl_url, output->data_url, session->consumer, new_output,
+ &session->snapshot);
+ if (ret < 0) {
+ if (ret == -ENOMEM) {
+ ret = LTTNG_ERR_NOMEM;
+ } else {
+ ret = LTTNG_ERR_INVALID;
+ }
+ goto free_error;
+ }
+
+ rcu_read_lock();
+ snapshot_add_output(&session->snapshot, new_output);
+ if (id) {
+ *id = new_output->id;
+ }
+ rcu_read_unlock();
+
+ return LTTNG_OK;
+
+free_error:
+ snapshot_output_destroy(new_output);
+error:
+ return ret;
+}
+
+/*
+ * Command LTTNG_SNAPSHOT_DEL_OUTPUT from lib lttng ctl.
+ *
+ * Return LTTNG_OK on success or else a LTTNG_ERR code.
+ */
+int cmd_snapshot_del_output(struct ltt_session *session,
+ struct lttng_snapshot_output *output)
+{
+ int ret;
+ struct snapshot_output *sout = NULL;
+
+ assert(session);
+ assert(output);
+
+ rcu_read_lock();
+
+ /*
+ * Permission denied to create an output if the session is not
+ * set in no output mode.
+ */
+ if (session->output_traces) {
+ ret = LTTNG_ERR_EPERM;
+ goto error;
+ }
+
+ if (output->id) {
+ DBG("Cmd snapshot del output id %" PRIu32 " for session %s", output->id,
+ session->name);
+ sout = snapshot_find_output_by_id(output->id, &session->snapshot);
+ } else if (*output->name != '\0') {
+ DBG("Cmd snapshot del output name %s for session %s", output->name,
+ session->name);
+ sout = snapshot_find_output_by_name(output->name, &session->snapshot);
+ }
+ if (!sout) {
+ ret = LTTNG_ERR_INVALID;
+ goto error;
+ }
+
+ snapshot_delete_output(&session->snapshot, sout);
+ snapshot_output_destroy(sout);
+ ret = LTTNG_OK;
+
+error:
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Command LTTNG_SNAPSHOT_LIST_OUTPUT from lib lttng ctl.
+ *
+ * If no output is available, outputs is untouched and 0 is returned.
+ *
+ * Return the size of the newly allocated outputs or a negative LTTNG_ERR code.
+ */
+ssize_t cmd_snapshot_list_outputs(struct ltt_session *session,
+ struct lttng_snapshot_output **outputs)
+{
+ int ret, idx = 0;
+ struct lttng_snapshot_output *list = NULL;
+ struct lttng_ht_iter iter;
+ struct snapshot_output *output;
+
+ assert(session);
+ assert(outputs);
+
+ DBG("Cmd snapshot list outputs for session %s", session->name);
+
+ /*
+ * Permission denied to create an output if the session is not
+ * set in no output mode.
+ */
+ if (session->output_traces) {
+ ret = -LTTNG_ERR_EPERM;
+ goto error;
+ }
+
+ if (session->snapshot.nb_output == 0) {
+ ret = 0;
+ goto error;
+ }
+
+ list = zmalloc(session->snapshot.nb_output * sizeof(*list));
+ if (!list) {
+ ret = -LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ /* Copy list from session to the new list object. */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(session->snapshot.output_ht->ht, &iter.iter,
+ output, node.node) {
+ assert(output->consumer);
+ list[idx].id = output->id;
+ list[idx].max_size = output->max_size;
+ strncpy(list[idx].name, output->name, sizeof(list[idx].name));
+ if (output->consumer->type == CONSUMER_DST_LOCAL) {
+ strncpy(list[idx].ctrl_url, output->consumer->dst.trace_path,
+ sizeof(list[idx].ctrl_url));
+ } else {
+ /* Control URI. */
+ ret = uri_to_str_url(&output->consumer->dst.net.control,
+ list[idx].ctrl_url, sizeof(list[idx].ctrl_url));
+ if (ret < 0) {
+ ret = -LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ /* Data URI. */
+ ret = uri_to_str_url(&output->consumer->dst.net.data,
+ list[idx].data_url, sizeof(list[idx].data_url));
+ if (ret < 0) {
+ ret = -LTTNG_ERR_NOMEM;
+ goto error;
+ }
+ }
+ idx++;
+ }
+
+ *outputs = list;
+ list = NULL;
+ ret = session->snapshot.nb_output;
+error:
+ free(list);
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Send relayd sockets from snapshot output to consumer. Ignore request if the
+ * snapshot output is *not* set with a remote destination.
+ *
+ * Return 0 on success or a LTTNG_ERR code.
+ */
+static int set_relayd_for_snapshot(struct consumer_output *consumer,
+ struct snapshot_output *snap_output, struct ltt_session *session)
+{
+ int ret = LTTNG_OK;
+ struct lttng_ht_iter iter;
+ struct consumer_socket *socket;
+
+ assert(consumer);
+ assert(snap_output);
+ assert(session);
+
+ DBG2("Set relayd object from snapshot output");
+
+ /* Ignore if snapshot consumer output is not network. */
+ if (snap_output->consumer->type != CONSUMER_DST_NET) {
+ goto error;
+ }
+
+ /*
+ * For each consumer socket, create and send the relayd object of the
+ * snapshot output.
+ */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(snap_output->consumer->socks->ht, &iter.iter,
+ socket, node.node) {
+ ret = send_consumer_relayd_sockets(0, session->id,
+ snap_output->consumer, socket,
+ session->name, session->hostname,
+ session->live_timer);
+ if (ret != LTTNG_OK) {
+ rcu_read_unlock();
+ goto error;
+ }
+ }
+ rcu_read_unlock();
+
+error:
+ return ret;
+}
+
+/*
+ * Record a kernel snapshot.
+ *
+ * Return LTTNG_OK on success or a LTTNG_ERR code.
+ */
+static int record_kernel_snapshot(struct ltt_kernel_session *ksess,
+ struct snapshot_output *output, struct ltt_session *session,
+ int wait, uint64_t max_stream_size)
+{
+ int ret;
+
+ assert(ksess);
+ assert(output);
+ assert(session);
+
+ /* Get the datetime for the snapshot output directory. */
+ ret = utils_get_current_time_str("%Y%m%d-%H%M%S", output->datetime,
+ sizeof(output->datetime));
+ if (!ret) {
+ ret = LTTNG_ERR_INVALID;
+ goto error;
+ }
+
+ /*
+ * Copy kernel session sockets so we can communicate with the right
+ * consumer for the snapshot record command.
+ */
+ ret = consumer_copy_sockets(output->consumer, ksess->consumer);
+ if (ret < 0) {
+ ret = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ ret = set_relayd_for_snapshot(ksess->consumer, output, session);
+ if (ret != LTTNG_OK) {
+ goto error_snapshot;
+ }
+
+ ret = kernel_snapshot_record(ksess, output, wait, max_stream_size);
+ if (ret != LTTNG_OK) {
+ goto error_snapshot;
+ }
+
+ ret = LTTNG_OK;
+
+error_snapshot:
+ /* Clean up copied sockets so this output can use some other later on. */
+ consumer_destroy_output_sockets(output->consumer);
+error:
+ return ret;
+}
+
+/*
+ * Record a UST snapshot.
+ *
+ * Return 0 on success or a LTTNG_ERR error code.
+ */
+static int record_ust_snapshot(struct ltt_ust_session *usess,
+ struct snapshot_output *output, struct ltt_session *session,
+ int wait, uint64_t max_stream_size)
+{
+ int ret;
+
+ assert(usess);
+ assert(output);
+ assert(session);
+
+ /* Get the datetime for the snapshot output directory. */
+ ret = utils_get_current_time_str("%Y%m%d-%H%M%S", output->datetime,
+ sizeof(output->datetime));
+ if (!ret) {
+ ret = LTTNG_ERR_INVALID;
+ goto error;
+ }
+
+ /*
+ * Copy UST session sockets so we can communicate with the right
+ * consumer for the snapshot record command.
+ */
+ ret = consumer_copy_sockets(output->consumer, usess->consumer);
+ if (ret < 0) {
+ ret = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+
+ ret = set_relayd_for_snapshot(usess->consumer, output, session);
+ if (ret != LTTNG_OK) {
+ goto error_snapshot;
+ }
+
+ ret = ust_app_snapshot_record(usess, output, wait, max_stream_size);
+ if (ret < 0) {
+ switch (-ret) {
+ case EINVAL:
+ ret = LTTNG_ERR_INVALID;
+ break;
+ case ENODATA:
+ ret = LTTNG_ERR_SNAPSHOT_NODATA;
+ break;
+ default:
+ ret = LTTNG_ERR_SNAPSHOT_FAIL;
+ break;
+ }
+ goto error_snapshot;
+ }
+
+ ret = LTTNG_OK;
+
+error_snapshot:
+ /* Clean up copied sockets so this output can use some other later on. */
+ consumer_destroy_output_sockets(output->consumer);
+error:
+ return ret;
+}
+
+/*
+ * Return the biggest subbuffer size of all channels in the given session.
+ */
+static uint64_t get_session_max_subbuf_size(struct ltt_session *session)
+{
+ uint64_t max_size = 0;
+
+ assert(session);
+
+ if (session->kernel_session) {
+ struct ltt_kernel_channel *chan;
+ struct ltt_kernel_session *ksess = session->kernel_session;
+
+ /*
+ * For each channel, add to the max size the size of each subbuffer
+ * multiplied by their sized.
+ */
+ cds_list_for_each_entry(chan, &ksess->channel_list.head, list) {
+ if (chan->channel->attr.subbuf_size > max_size) {
+ max_size = chan->channel->attr.subbuf_size;
+ }
+ }
+ }
+
+ if (session->ust_session) {
+ struct lttng_ht_iter iter;
+ struct ltt_ust_channel *uchan;
+ struct ltt_ust_session *usess = session->ust_session;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(usess->domain_global.channels->ht, &iter.iter,
+ uchan, node.node) {
+ if (uchan->attr.subbuf_size > max_size) {
+ max_size = uchan->attr.subbuf_size;
+ }
+ }
+ rcu_read_unlock();
+ }
+
+ return max_size;
+}
+
+/*
+ * Returns the total number of streams for a session or a negative value
+ * on error.
+ */
+static unsigned int get_session_nb_streams(struct ltt_session *session)
+{
+ unsigned int total_streams = 0;
+
+ if (session->kernel_session) {
+ struct ltt_kernel_session *ksess = session->kernel_session;
+
+ total_streams += ksess->stream_count_global;
+ }
+
+ if (session->ust_session) {
+ struct ltt_ust_session *usess = session->ust_session;
+
+ total_streams += ust_app_get_nb_stream(usess);
+ }
+
+ return total_streams;
+}
+
+/*
+ * 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,
+ struct lttng_snapshot_output *output, int wait)
+{
+ int ret = LTTNG_OK;
+ unsigned int use_tmp_output = 0;
+ struct snapshot_output tmp_output;
+ unsigned int nb_streams, snapshot_success = 0;
+ uint64_t session_max_size = 0, max_stream_size = 0;
+
+ assert(session);
+ assert(output);
+
+ DBG("Cmd snapshot record for session %s", session->name);
+
+ /*
+ * Permission denied to create an output if the session is not
+ * set in no output mode.
+ */
+ if (session->output_traces) {
+ ret = LTTNG_ERR_EPERM;
+ goto error;
+ }
+
+ /* The session needs to be started at least once. */
+ if (!session->has_been_started) {
+ ret = LTTNG_ERR_START_SESSION_ONCE;
+ goto error;
+ }
+
+ /* Use temporary output for the session. */
+ if (*output->ctrl_url != '\0') {
+ ret = snapshot_output_init(output->max_size, output->name,
+ output->ctrl_url, output->data_url, session->consumer,
+ &tmp_output, NULL);
+ if (ret < 0) {
+ if (ret == -ENOMEM) {
+ ret = LTTNG_ERR_NOMEM;
+ } else {
+ ret = LTTNG_ERR_INVALID;
+ }
+ goto error;
+ }
+ /* Use the global session count for the temporary snapshot. */
+ tmp_output.nb_snapshot = session->snapshot.nb_snapshot;
+ use_tmp_output = 1;
+ }
+
+ /*
+ * Get the session maximum size for a snapshot meaning it will compute the
+ * size of all streams from all domain.
+ */
+ max_stream_size = get_session_max_subbuf_size(session);
+
+ nb_streams = get_session_nb_streams(session);
+ if (nb_streams) {
+ /*
+ * The maximum size of the snapshot is the number of streams multiplied
+ * by the biggest subbuf size of all channels in a session which is the
+ * maximum stream size available for each stream. The session max size
+ * is now checked against the snapshot max size value given by the user
+ * and if lower, an error is returned.
+ */
+ session_max_size = max_stream_size * nb_streams;
+ }
+
+ DBG3("Snapshot max size is %" PRIu64 " for max stream size of %" PRIu64,
+ session_max_size, max_stream_size);
+
+ /*
+ * If we use a temporary output, check right away if the max size fits else
+ * for each output the max size will be checked.
+ */
+ if (use_tmp_output &&
+ (tmp_output.max_size != 0 &&
+ tmp_output.max_size < session_max_size)) {
+ ret = LTTNG_ERR_MAX_SIZE_INVALID;
+ goto error;
+ }
+
+ if (session->kernel_session) {
+ struct ltt_kernel_session *ksess = session->kernel_session;
+
+ if (use_tmp_output) {
+ ret = record_kernel_snapshot(ksess, &tmp_output, session,
+ wait, max_stream_size);
+ if (ret != LTTNG_OK) {
+ goto error;
+ }
+ snapshot_success = 1;
+ } else {
+ struct snapshot_output *sout;
+ struct lttng_ht_iter iter;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(session->snapshot.output_ht->ht,
+ &iter.iter, sout, node.node) {
+ /*
+ * Make a local copy of the output and assign the possible
+ * temporary value given by the caller.
+ */
+ memset(&tmp_output, 0, sizeof(tmp_output));
+ memcpy(&tmp_output, sout, sizeof(tmp_output));
+
+ /* Use temporary max size. */
+ if (output->max_size != (uint64_t) -1ULL) {
+ tmp_output.max_size = output->max_size;
+ }
+
+ if (tmp_output.max_size != 0 &&
+ tmp_output.max_size < session_max_size) {
+ rcu_read_unlock();
+ ret = LTTNG_ERR_MAX_SIZE_INVALID;
+ goto error;
+ }
+
+ /* Use temporary name. */
+ if (*output->name != '\0') {
+ strncpy(tmp_output.name, output->name,
+ sizeof(tmp_output.name));
+ }
+
+ tmp_output.nb_snapshot = session->snapshot.nb_snapshot;
+
+ ret = record_kernel_snapshot(ksess, &tmp_output,
+ session, wait, max_stream_size);
+ if (ret != LTTNG_OK) {
+ rcu_read_unlock();
+ goto error;
+ }
+ snapshot_success = 1;
+ }
+ rcu_read_unlock();
+ }
+ }
+
+ if (session->ust_session) {
+ struct ltt_ust_session *usess = session->ust_session;
+
+ if (use_tmp_output) {
+ ret = record_ust_snapshot(usess, &tmp_output, session,
+ wait, max_stream_size);
+ if (ret != LTTNG_OK) {
+ goto error;
+ }
+ snapshot_success = 1;
+ } else {
+ struct snapshot_output *sout;
+ struct lttng_ht_iter iter;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(session->snapshot.output_ht->ht,
+ &iter.iter, sout, node.node) {
+ /*
+ * Make a local copy of the output and assign the possible
+ * temporary value given by the caller.
+ */
+ memset(&tmp_output, 0, sizeof(tmp_output));
+ memcpy(&tmp_output, sout, sizeof(tmp_output));
+
+ /* Use temporary max size. */
+ if (output->max_size != (uint64_t) -1ULL) {
+ tmp_output.max_size = output->max_size;
+ }
+
+ if (tmp_output.max_size != 0 &&
+ tmp_output.max_size < session_max_size) {
+ rcu_read_unlock();
+ ret = LTTNG_ERR_MAX_SIZE_INVALID;
+ goto error;
+ }
+
+ /* Use temporary name. */
+ if (*output->name != '\0') {
+ strncpy(tmp_output.name, output->name,
+ sizeof(tmp_output.name));
+ }
+
+ tmp_output.nb_snapshot = session->snapshot.nb_snapshot;
+
+ ret = record_ust_snapshot(usess, &tmp_output, session,
+ wait, max_stream_size);
+ if (ret != LTTNG_OK) {
+ rcu_read_unlock();
+ goto error;
+ }
+ snapshot_success = 1;
+ }
+ rcu_read_unlock();
+ }
+ }
+
+ if (snapshot_success) {
+ session->snapshot.nb_snapshot++;
+ } else {
+ ret = LTTNG_ERR_SNAPSHOT_FAIL;
+ }
+
+error:
+ return ret;
+}
+