+/*
+ * Copyright (C) 2019 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2019 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License, version 2.1 only,
+ * as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define _LGPL_SOURCE
+#include <assert.h>
+#include <string.h>
+
+#include <lttng/lttng-error.h>
+#include <lttng/clear.h>
+#include <lttng/clear-handle.h>
+#include <common/sessiond-comm/sessiond-comm.h>
+#include <common/macros.h>
+#include <common/compat/poll.h>
+#include <common/dynamic-buffer.h>
+#include <common/buffer-view.h>
+#include <common/optional.h>
+
+#include "lttng-ctl-helper.h"
+
+enum communication_state {
+ COMMUNICATION_STATE_RECEIVE_LTTNG_MSG,
+ COMMUNICATION_STATE_RECEIVE_COMMAND_HEADER,
+ COMMUNICATION_STATE_END,
+ COMMUNICATION_STATE_ERROR,
+};
+
+struct lttng_clear_handle {
+ LTTNG_OPTIONAL(enum lttng_error_code) clear_return_code;
+ struct {
+ int socket;
+ struct lttng_poll_event events;
+ size_t bytes_left_to_receive;
+ enum communication_state state;
+ struct lttng_dynamic_buffer buffer;
+ LTTNG_OPTIONAL(size_t) data_size;
+ } communication;
+};
+
+void lttng_clear_handle_destroy(struct lttng_clear_handle *handle)
+{
+ int ret;
+
+ if (!handle) {
+ return;
+ }
+
+ if (handle->communication.socket >= 0) {
+ ret = close(handle->communication.socket);
+ if (ret) {
+ PERROR("Failed to close lttng-sessiond command socket");
+ }
+ }
+ lttng_poll_clean(&handle->communication.events);
+ lttng_dynamic_buffer_reset(&handle->communication.buffer);
+ free(handle);
+}
+
+static
+struct lttng_clear_handle *lttng_clear_handle_create(int sessiond_socket)
+{
+ int ret;
+ struct lttng_clear_handle *handle = zmalloc(sizeof(*handle));
+
+ if (!handle) {
+ goto end;
+ }
+ lttng_dynamic_buffer_init(&handle->communication.buffer);
+ handle->communication.socket = sessiond_socket;
+ ret = lttng_poll_create(&handle->communication.events, 1, 0);
+ if (ret) {
+ goto error;
+ }
+
+ ret = lttng_poll_add(&handle->communication.events, sessiond_socket,
+ LPOLLIN | LPOLLHUP | LPOLLRDHUP | LPOLLERR);
+ if (ret) {
+ goto error;
+ }
+
+ handle->communication.bytes_left_to_receive =
+ sizeof(struct lttcomm_lttng_msg);
+ handle->communication.state = COMMUNICATION_STATE_RECEIVE_LTTNG_MSG;
+end:
+ return handle;
+error:
+ lttng_clear_handle_destroy(handle);
+ return NULL;
+}
+
+static
+int handle_state_transition(struct lttng_clear_handle *handle)
+{
+ int ret = 0;
+
+ assert(handle->communication.bytes_left_to_receive == 0);
+
+ switch (handle->communication.state) {
+ case COMMUNICATION_STATE_RECEIVE_LTTNG_MSG:
+ {
+ const struct lttcomm_lttng_msg *msg =
+ (typeof(msg)) handle->communication.buffer.data;
+
+ LTTNG_OPTIONAL_SET(&handle->clear_return_code,
+ (enum lttng_error_code) msg->ret_code);
+ if (handle->clear_return_code.value != LTTNG_OK) {
+ handle->communication.state = COMMUNICATION_STATE_END;
+ break;
+ } else if (msg->cmd_header_size != 0 || msg->data_size != 0) {
+ handle->communication.state = COMMUNICATION_STATE_ERROR;
+ ret = -1;
+ break;
+ }
+
+ handle->communication.state = COMMUNICATION_STATE_END;
+ handle->communication.bytes_left_to_receive = 0;
+ LTTNG_OPTIONAL_SET(&handle->communication.data_size, 0);
+ ret = lttng_dynamic_buffer_set_size(
+ &handle->communication.buffer, 0);
+ assert(!ret);
+ break;
+ }
+ default:
+ abort();
+ }
+
+ /* Clear reception buffer on state transition. */
+ if (lttng_dynamic_buffer_set_size(&handle->communication.buffer, 0)) {
+ abort();
+ }
+ return ret;
+}
+
+static
+int handle_incoming_data(struct lttng_clear_handle *handle)
+{
+ int ret;
+ ssize_t comm_ret;
+ const size_t original_buffer_size = handle->communication.buffer.size;
+
+ /* Reserve space for reception. */
+ ret = lttng_dynamic_buffer_set_size(&handle->communication.buffer,
+ original_buffer_size + handle->communication.bytes_left_to_receive);
+ if (ret) {
+ goto end;
+ }
+
+ comm_ret = lttcomm_recv_unix_sock(handle->communication.socket,
+ handle->communication.buffer.data + original_buffer_size,
+ handle->communication.bytes_left_to_receive);
+ if (comm_ret <= 0) {
+ ret = -1;
+ goto end;
+ }
+
+ handle->communication.bytes_left_to_receive -= comm_ret;
+ if (handle->communication.bytes_left_to_receive == 0) {
+ ret = handle_state_transition(handle);
+ } else {
+ ret = lttng_dynamic_buffer_set_size(
+ &handle->communication.buffer,
+ original_buffer_size + comm_ret);
+ }
+end:
+ return ret;
+}
+
+extern enum lttng_clear_handle_status
+ lttng_clear_handle_wait_for_completion(
+ struct lttng_clear_handle *handle, int timeout_ms)
+{
+ int ret;
+ enum lttng_clear_handle_status status;
+ unsigned long time_left_ms = 0;
+ const bool has_timeout = timeout_ms > 0;
+ struct timespec initial_time;
+
+ if (handle->communication.state == COMMUNICATION_STATE_ERROR) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ } else if (handle->communication.state == COMMUNICATION_STATE_END) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_COMPLETED;
+ goto end;
+ }
+ if (has_timeout) {
+ ret = lttng_clock_gettime(CLOCK_MONOTONIC, &initial_time);
+ if (ret) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+ time_left_ms = (unsigned long) timeout_ms;
+ }
+
+ while (handle->communication.state != COMMUNICATION_STATE_END &&
+ (time_left_ms || !has_timeout)) {
+ int ret;
+ uint32_t revents;
+ struct timespec current_time, diff;
+ unsigned long diff_ms;
+
+ ret = lttng_poll_wait(&handle->communication.events,
+ has_timeout ? time_left_ms : -1);
+ if (ret == 0) {
+ /* timeout */
+ break;
+ } else if (ret < 0) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+
+ /* The sessiond connection socket is the only monitored fd. */
+ revents = LTTNG_POLL_GETEV(&handle->communication.events, 0);
+ if (revents & LPOLLIN) {
+ ret = handle_incoming_data(handle);
+ if (ret) {
+ handle->communication.state =
+ COMMUNICATION_STATE_ERROR;
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+ } else {
+ handle->communication.state = COMMUNICATION_STATE_ERROR;
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+ if (!has_timeout) {
+ continue;
+ }
+
+ ret = lttng_clock_gettime(CLOCK_MONOTONIC, ¤t_time);
+ if (ret) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+ diff = timespec_abs_diff(initial_time, current_time);
+ ret = timespec_to_ms(diff, &diff_ms);
+ if (ret) {
+ ERR("Failed to compute elapsed time while waiting for completion");
+ status = LTTNG_CLEAR_HANDLE_STATUS_ERROR;
+ goto end;
+ }
+ DBG("%lums elapsed while waiting for session clear completion",
+ diff_ms);
+ diff_ms = max_t(unsigned long, diff_ms, 1);
+ diff_ms = min_t(unsigned long, diff_ms, time_left_ms);
+ time_left_ms -= diff_ms;
+ }
+
+ status = handle->communication.state == COMMUNICATION_STATE_END ?
+ LTTNG_CLEAR_HANDLE_STATUS_COMPLETED :
+ LTTNG_CLEAR_HANDLE_STATUS_TIMEOUT;
+end:
+ return status;
+}
+
+extern enum lttng_clear_handle_status
+ lttng_clear_handle_get_result(
+ const struct lttng_clear_handle *handle,
+ enum lttng_error_code *result)
+{
+ enum lttng_clear_handle_status status =
+ LTTNG_CLEAR_HANDLE_STATUS_OK;
+
+ if (!handle->clear_return_code.is_set) {
+ status = LTTNG_CLEAR_HANDLE_STATUS_INVALID;
+ goto end;
+ }
+ *result = handle->clear_return_code.value;
+end:
+ return status;
+}
+
+/*
+ * Clear the session
+ */
+enum lttng_error_code lttng_clear_session(const char *session_name,
+ struct lttng_clear_handle **_handle)
+{
+ enum lttng_error_code ret_code = LTTNG_OK;
+ struct lttng_clear_handle *handle = NULL;
+ struct lttcomm_session_msg lsm = {
+ .cmd_type = LTTNG_CLEAR_SESSION,
+ };
+ int sessiond_socket = -1;
+ ssize_t comm_ret;
+ int ret;
+
+ if (session_name == NULL) {
+ ret_code = LTTNG_ERR_INVALID;
+ goto error;
+ }
+ ret = lttng_strncpy(lsm.session.name, session_name,
+ sizeof(lsm.session.name));
+ if (ret) {
+ ret_code = LTTNG_ERR_INVALID;
+ goto error;
+ }
+ ret = connect_sessiond();
+ if (ret < 0) {
+ ret_code = LTTNG_ERR_NO_SESSIOND;
+ goto error;
+ } else {
+ sessiond_socket = ret;
+ }
+ handle = lttng_clear_handle_create(sessiond_socket);
+ if (!handle) {
+ ret_code = LTTNG_ERR_NOMEM;
+ goto error;
+ }
+ comm_ret = lttcomm_send_creds_unix_sock(sessiond_socket, &lsm, sizeof(lsm));
+ if (comm_ret < 0) {
+ ret_code = LTTNG_ERR_FATAL;
+ goto error;
+ }
+ sessiond_socket = -1;
+
+error:
+ /* Transfer the handle to the caller. */
+ if (_handle) {
+ *_handle = handle;
+ handle = NULL;
+ }
+ if (sessiond_socket >= 0) {
+ ret = close(sessiond_socket);
+ if (ret < 0) {
+ PERROR("Failed to close the LTTng session daemon connection socket");
+ }
+ }
+ if (handle) {
+ lttng_clear_handle_destroy(handle);
+ }
+ return ret_code;
+}