+/*
+ * Command LTTNG_ROTATE_SESSION from the lttng-ctl library.
+ *
+ * Ask the consumer to rotate the session output directory.
+ * The session lock must be held.
+ *
+ * Returns LTTNG_OK on success or else a negative LTTng error code.
+ */
+int cmd_rotate_session(struct ltt_session *session,
+ struct lttng_rotate_session_return *rotate_return,
+ bool quiet_rotation)
+{
+ int ret;
+ uint64_t ongoing_rotation_chunk_id;
+ enum lttng_error_code cmd_ret = LTTNG_OK;
+ struct lttng_trace_chunk *chunk_being_archived = NULL;
+ struct lttng_trace_chunk *new_trace_chunk = NULL;
+ enum lttng_trace_chunk_status chunk_status;
+ bool failed_to_rotate = false;
+ enum lttng_error_code rotation_fail_code = LTTNG_OK;
+
+ assert(session);
+
+ if (!session->has_been_started) {
+ cmd_ret = LTTNG_ERR_START_SESSION_ONCE;
+ goto end;
+ }
+
+ /*
+ * Explicit rotation is not supported for live sessions.
+ * However, live sessions can perform a quiet rotation on
+ * destroy.
+ * Rotation is not supported for snapshot traces (no output).
+ */
+ if ((!quiet_rotation && session->live_timer) ||
+ !session->output_traces) {
+ cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE;
+ goto end;
+ }
+
+ /* Unsupported feature in lttng-relayd before 2.11. */
+ if (!quiet_rotation && session->consumer->type == CONSUMER_DST_NET &&
+ (session->consumer->relay_major_version == 2 &&
+ session->consumer->relay_minor_version < 11)) {
+ cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_RELAY;
+ goto end;
+ }
+
+ /* Unsupported feature in lttng-modules before 2.8 (lack of sequence number). */
+ if (session->kernel_session && !kernel_supports_ring_buffer_packet_sequence_number()) {
+ cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL;
+ goto end;
+ }
+
+ if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) {
+ DBG("Refusing to launch a rotation; a rotation is already in progress for session %s",
+ session->name);
+ cmd_ret = LTTNG_ERR_ROTATION_PENDING;
+ goto end;
+ }
+
+ /*
+ * After a stop, we only allow one rotation to occur, the other ones are
+ * useless until a new start.
+ */
+ if (session->rotated_after_last_stop) {
+ DBG("Session \"%s\" was already rotated after stop, refusing rotation",
+ session->name);
+ cmd_ret = LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP;
+ goto end;
+ }
+ if (session->active) {
+ new_trace_chunk = session_create_new_trace_chunk(session, NULL,
+ NULL, NULL);
+ if (!new_trace_chunk) {
+ cmd_ret = LTTNG_ERR_CREATE_DIR_FAIL;
+ goto error;
+ }
+ }
+
+ /*
+ * The current trace chunk becomes the chunk being archived.
+ *
+ * After this point, "chunk_being_archived" must absolutely
+ * be closed on the consumer(s), otherwise it will never be
+ * cleaned-up, which will result in a leak.
+ */
+ ret = session_set_trace_chunk(session, new_trace_chunk,
+ &chunk_being_archived);
+ if (ret) {
+ cmd_ret = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER;
+ goto error;
+ }
+
+ if (session->kernel_session) {
+ cmd_ret = kernel_rotate_session(session);
+ if (cmd_ret != LTTNG_OK) {
+ failed_to_rotate = true;
+ rotation_fail_code = cmd_ret;
+ }
+ }
+ if (session->ust_session) {
+ cmd_ret = ust_app_rotate_session(session);
+ if (cmd_ret != LTTNG_OK) {
+ failed_to_rotate = true;
+ rotation_fail_code = cmd_ret;
+ }
+ }
+
+ if (!session->active) {
+ session->rotated_after_last_stop = true;
+ }
+
+ if (!chunk_being_archived) {
+ DBG("Rotating session \"%s\" from a \"NULL\" trace chunk to a new trace chunk, skipping completion check",
+ session->name);
+ if (failed_to_rotate) {
+ cmd_ret = rotation_fail_code;
+ goto error;
+ }
+ cmd_ret = LTTNG_OK;
+ goto end;
+ }
+
+ session->rotation_state = LTTNG_ROTATION_STATE_ONGOING;
+ chunk_status = lttng_trace_chunk_get_id(chunk_being_archived,
+ &ongoing_rotation_chunk_id);
+ assert(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK);
+
+ ret = session_close_trace_chunk(session, chunk_being_archived,
+ quiet_rotation ?
+ NULL :
+ &((enum lttng_trace_chunk_command_type){
+ LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED}),
+ session->last_chunk_path);
+ if (ret) {
+ cmd_ret = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER;
+ goto error;
+ }
+
+ if (failed_to_rotate) {
+ cmd_ret = rotation_fail_code;
+ goto error;
+ }
+
+ session->quiet_rotation = quiet_rotation;
+ ret = timer_session_rotation_pending_check_start(session,
+ DEFAULT_ROTATE_PENDING_TIMER);
+ if (ret) {
+ cmd_ret = LTTNG_ERR_UNK;
+ goto error;
+ }
+
+ if (rotate_return) {
+ rotate_return->rotation_id = ongoing_rotation_chunk_id;
+ }
+
+ session->chunk_being_archived = chunk_being_archived;
+ chunk_being_archived = NULL;
+ if (!quiet_rotation) {
+ ret = notification_thread_command_session_rotation_ongoing(
+ notification_thread_handle,
+ session->name, session->uid, session->gid,
+ ongoing_rotation_chunk_id);
+ if (ret != LTTNG_OK) {
+ ERR("Failed to notify notification thread that a session rotation is ongoing for session %s",
+ session->name);
+ cmd_ret = ret;
+ }
+ }
+
+ DBG("Cmd rotate session %s, archive_id %" PRIu64 " sent",
+ session->name, ongoing_rotation_chunk_id);
+end:
+ lttng_trace_chunk_put(new_trace_chunk);
+ lttng_trace_chunk_put(chunk_being_archived);
+ ret = (cmd_ret == LTTNG_OK) ? cmd_ret : -((int) cmd_ret);
+ return ret;
+error:
+ if (session_reset_rotation_state(session,
+ LTTNG_ROTATION_STATE_ERROR)) {
+ ERR("Failed to reset rotation state of session \"%s\"",
+ session->name);
+ }
+ goto end;
+}
+
+/*
+ * Command LTTNG_ROTATION_GET_INFO from the lttng-ctl library.
+ *
+ * Check if the session has finished its rotation.
+ *
+ * Return LTTNG_OK on success or else an LTTNG_ERR code.
+ */
+int cmd_rotate_get_info(struct ltt_session *session,
+ struct lttng_rotation_get_info_return *info_return,
+ uint64_t rotation_id)
+{
+ enum lttng_error_code cmd_ret = LTTNG_OK;
+ enum lttng_rotation_state rotation_state;
+
+ DBG("Cmd rotate_get_info session %s, rotation id %" PRIu64, session->name,
+ session->most_recent_chunk_id.value);
+
+ if (session->chunk_being_archived) {
+ enum lttng_trace_chunk_status chunk_status;
+ uint64_t chunk_id;
+
+ chunk_status = lttng_trace_chunk_get_id(
+ session->chunk_being_archived,
+ &chunk_id);
+ assert(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK);
+
+ rotation_state = rotation_id == chunk_id ?
+ LTTNG_ROTATION_STATE_ONGOING :
+ LTTNG_ROTATION_STATE_EXPIRED;
+ } else {
+ if (session->last_archived_chunk_id.is_set &&
+ rotation_id != session->last_archived_chunk_id.value) {
+ rotation_state = LTTNG_ROTATION_STATE_EXPIRED;
+ } else {
+ rotation_state = session->rotation_state;
+ }
+ }
+
+ switch (rotation_state) {
+ case LTTNG_ROTATION_STATE_NO_ROTATION:
+ DBG("Reporting that no rotation has occurred within the lifetime of session \"%s\"",
+ session->name);
+ goto end;
+ case LTTNG_ROTATION_STATE_EXPIRED:
+ DBG("Reporting that the rotation state of rotation id %" PRIu64 " of session \"%s\" has expired",
+ rotation_id, session->name);
+ break;
+ case LTTNG_ROTATION_STATE_ONGOING:
+ DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is still pending",
+ rotation_id, session->name);
+ break;
+ case LTTNG_ROTATION_STATE_COMPLETED:
+ {
+ int fmt_ret;
+ char *chunk_path;
+ char *current_tracing_path_reply;
+ size_t current_tracing_path_reply_len;
+
+ DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is completed",
+ rotation_id, session->name);
+
+ switch (session_get_consumer_destination_type(session)) {
+ case CONSUMER_DST_LOCAL:
+ current_tracing_path_reply =
+ info_return->location.local.absolute_path;
+ current_tracing_path_reply_len =
+ sizeof(info_return->location.local.absolute_path);
+ info_return->location_type =
+ (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_LOCAL;
+ fmt_ret = asprintf(&chunk_path,
+ "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY "/%s",
+ session_get_base_path(session),
+ session->last_archived_chunk_name);
+ if (fmt_ret == -1) {
+ PERROR("Failed to format the path of the last archived trace chunk");
+ info_return->status = LTTNG_ROTATION_STATUS_ERROR;
+ cmd_ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+ break;
+ case CONSUMER_DST_NET:
+ {
+ uint16_t ctrl_port, data_port;
+
+ current_tracing_path_reply =
+ info_return->location.relay.relative_path;
+ current_tracing_path_reply_len =
+ sizeof(info_return->location.relay.relative_path);
+ /* Currently the only supported relay protocol. */
+ info_return->location.relay.protocol =
+ (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_RELAY_PROTOCOL_TYPE_TCP;
+
+ fmt_ret = lttng_strncpy(info_return->location.relay.host,
+ session_get_net_consumer_hostname(session),
+ sizeof(info_return->location.relay.host));
+ if (fmt_ret) {
+ ERR("Failed to copy host name to rotate_get_info reply");
+ info_return->status = LTTNG_ROTATION_STATUS_ERROR;
+ cmd_ret = LTTNG_ERR_SET_URL;
+ goto end;
+ }
+
+ session_get_net_consumer_ports(session, &ctrl_port, &data_port);
+ info_return->location.relay.ports.control = ctrl_port;
+ info_return->location.relay.ports.data = data_port;
+ info_return->location_type =
+ (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_RELAY;
+ chunk_path = strdup(session->last_chunk_path);
+ if (!chunk_path) {
+ ERR("Failed to allocate the path of the last archived trace chunk");
+ info_return->status = LTTNG_ROTATION_STATUS_ERROR;
+ cmd_ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+ break;
+ }
+ default:
+ abort();
+ }
+
+ fmt_ret = lttng_strncpy(current_tracing_path_reply,
+ chunk_path, current_tracing_path_reply_len);
+ free(chunk_path);
+ if (fmt_ret) {
+ ERR("Failed to copy path of the last archived trace chunk to rotate_get_info reply");
+ info_return->status = LTTNG_ROTATION_STATUS_ERROR;
+ cmd_ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ break;
+ }
+ case LTTNG_ROTATION_STATE_ERROR:
+ DBG("Reporting that an error occurred during rotation %" PRIu64 " of session \"%s\"",
+ rotation_id, session->name);
+ break;
+ default:
+ abort();
+ }
+
+ cmd_ret = LTTNG_OK;
+end:
+ info_return->status = (int32_t) rotation_state;
+ return cmd_ret;
+}
+
+/*
+ * Command LTTNG_ROTATION_SET_SCHEDULE from the lttng-ctl library.
+ *
+ * Configure the automatic rotation parameters.
+ * 'activate' to true means activate the rotation schedule type with 'new_value'.
+ * 'activate' to false means deactivate the rotation schedule and validate that
+ * 'new_value' has the same value as the currently active value.
+ *
+ * Return 0 on success or else a positive LTTNG_ERR code.
+ */
+int cmd_rotation_set_schedule(struct ltt_session *session,
+ bool activate, enum lttng_rotation_schedule_type schedule_type,
+ uint64_t new_value,
+ struct notification_thread_handle *notification_thread_handle)
+{
+ int ret;
+ uint64_t *parameter_value;
+
+ assert(session);
+
+ DBG("Cmd rotate set schedule session %s", session->name);
+
+ if (session->live_timer || !session->output_traces) {
+ DBG("Failing ROTATION_SET_SCHEDULE command as the rotation feature is not available for this session");
+ ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE;
+ goto end;
+ }
+
+ switch (schedule_type) {
+ case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD:
+ parameter_value = &session->rotate_size;
+ break;
+ case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC:
+ parameter_value = &session->rotate_timer_period;
+ if (new_value >= UINT_MAX) {
+ DBG("Failing ROTATION_SET_SCHEDULE command as the value requested for a periodic rotation schedule is invalid: %" PRIu64 " > %u (UINT_MAX)",
+ new_value, UINT_MAX);
+ ret = LTTNG_ERR_INVALID;
+ goto end;
+ }
+ break;
+ default:
+ WARN("Failing ROTATION_SET_SCHEDULE command on unknown schedule type");
+ ret = LTTNG_ERR_INVALID;
+ goto end;
+ }
+
+ /* Improper use of the API. */
+ if (new_value == -1ULL) {
+ WARN("Failing ROTATION_SET_SCHEDULE command as the value requested is -1");
+ ret = LTTNG_ERR_INVALID;
+ goto end;
+ }
+
+ /*
+ * As indicated in struct ltt_session's comments, a value of == 0 means
+ * this schedule rotation type is not in use.
+ *
+ * Reject the command if we were asked to activate a schedule that was
+ * already active.
+ */
+ if (activate && *parameter_value != 0) {
+ DBG("Failing ROTATION_SET_SCHEDULE (activate) command as the schedule is already active");
+ ret = LTTNG_ERR_ROTATION_SCHEDULE_SET;
+ goto end;
+ }
+
+ /*
+ * Reject the command if we were asked to deactivate a schedule that was
+ * not active.
+ */
+ if (!activate && *parameter_value == 0) {
+ DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as the schedule is already inactive");
+ ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET;
+ goto end;
+ }
+
+ /*
+ * Reject the command if we were asked to deactivate a schedule that
+ * doesn't exist.
+ */
+ if (!activate && *parameter_value != new_value) {
+ DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as an inexistant schedule was provided");
+ ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET;
+ goto end;
+ }
+
+ *parameter_value = activate ? new_value : 0;
+
+ switch (schedule_type) {
+ case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC:
+ if (activate && session->active) {
+ /*
+ * Only start the timer if the session is active,
+ * otherwise it will be started when the session starts.
+ */
+ ret = timer_session_rotation_schedule_timer_start(
+ session, new_value);
+ if (ret) {
+ ERR("Failed to enable session rotation timer in ROTATION_SET_SCHEDULE command");
+ ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+ } else {
+ ret = timer_session_rotation_schedule_timer_stop(
+ session);
+ if (ret) {
+ ERR("Failed to disable session rotation timer in ROTATION_SET_SCHEDULE command");
+ ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+ }
+ break;
+ case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD:
+ if (activate) {
+ ret = subscribe_session_consumed_size_rotation(session,
+ new_value, notification_thread_handle);
+ if (ret) {
+ ERR("Failed to enable consumed-size notification in ROTATION_SET_SCHEDULE command");
+ ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+ } else {
+ ret = unsubscribe_session_consumed_size_rotation(session,
+ notification_thread_handle);
+ if (ret) {
+ ERR("Failed to disable consumed-size notification in ROTATION_SET_SCHEDULE command");
+ ret = LTTNG_ERR_UNK;
+ goto end;
+ }
+
+ }
+ break;
+ default:
+ /* Would have been caught before. */
+ abort();
+ }
+
+ ret = LTTNG_OK;
+
+ goto end;
+
+end:
+ return ret;
+}
+
+/* Wait for a given path to be removed before continuing. */
+static enum lttng_error_code wait_on_path(void *path_data)
+{
+ const char *shm_path = path_data;
+
+ DBG("Waiting for the shm path at %s to be removed before completing session destruction",
+ shm_path);
+ while (true) {
+ int ret;
+ struct stat st;
+
+ ret = stat(shm_path, &st);
+ if (ret) {
+ if (errno != ENOENT) {
+ PERROR("stat() returned an error while checking for the existence of the shm path");
+ } else {
+ DBG("shm path no longer exists, completing the destruction of session");
+ }
+ break;
+ } else {
+ if (!S_ISDIR(st.st_mode)) {
+ ERR("The type of shm path %s returned by stat() is not a directory; aborting the wait for shm path removal",
+ shm_path);
+ break;
+ }
+ }
+ usleep(SESSION_DESTROY_SHM_PATH_CHECK_DELAY_US);
+ }
+ return LTTNG_OK;
+}
+
+/*
+ * Returns a pointer to a handler to run on completion of a command.
+ * Returns NULL if no handler has to be run for the last command executed.
+ */
+const struct cmd_completion_handler *cmd_pop_completion_handler(void)
+{
+ struct cmd_completion_handler *handler = current_completion_handler;
+
+ current_completion_handler = NULL;
+ return handler;
+}
+