bin_PROGRAMS = lttng-relayd
-lttng_relayd_SOURCES = main.c lttng-relayd.h utils.h utils.c cmd.h \
- index.c index.h live.c live.h ctf-trace.c ctf-trace.h \
- cmd-2-1.c cmd-2-1.h \
- cmd-2-2.c cmd-2-2.h \
- cmd-2-4.c cmd-2-4.h \
- cmd-2-11.c cmd-2-11.h \
- health-relayd.c health-relayd.h \
+lttng_relayd_SOURCES = main.cpp lttng-relayd.h utils.h utils.cpp cmd.h \
+ index.cpp index.h live.cpp live.h ctf-trace.cpp ctf-trace.h \
+ cmd-2-1.cpp cmd-2-1.h \
+ cmd-2-2.cpp cmd-2-2.h \
+ cmd-2-4.cpp cmd-2-4.h \
+ cmd-2-11.cpp cmd-2-11.h \
+ health-relayd.cpp health-relayd.h \
lttng-viewer-abi.h testpoint.h \
- viewer-stream.h viewer-stream.c \
- session.c session.h \
- stream.c stream.h \
- connection.c connection.h \
- viewer-session.c viewer-session.h \
- tracefile-array.c tracefile-array.h \
- tcp_keep_alive.c tcp_keep_alive.h \
- sessiond-trace-chunks.c sessiond-trace-chunks.h \
- backward-compatibility-group-by.c backward-compatibility-group-by.h
+ viewer-stream.h viewer-stream.cpp \
+ session.cpp session.h \
+ stream.cpp stream.h \
+ connection.cpp connection.h \
+ viewer-session.cpp viewer-session.h \
+ tracefile-array.cpp tracefile-array.h \
+ tcp_keep_alive.cpp tcp_keep_alive.h \
+ sessiond-trace-chunks.cpp sessiond-trace-chunks.h \
+ backward-compatibility-group-by.cpp backward-compatibility-group-by.h
# link on liblttngctl for check if relayd is already alive.
lttng_relayd_LDADD = $(URCU_LIBS) \
+++ /dev/null
-/*
- * Copyright (C) 2019 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#include "common/time.h"
-#include <regex.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <common/common.h>
-#include <common/defaults.h>
-#include <common/utils.h>
-
-#include "backward-compatibility-group-by.h"
-
-#define DATETIME_REGEX \
- ".*-[1-2][0-9][0-9][0-9][0-1][0-9][0-3][0-9]-[0-2][0-9][0-5][0-9][0-5][0-9]$"
-
-/*
- * Provide support for --group-output-by-session for producer >= 2.4 and < 2.11.
- * Take the stream path, extract all available information, craft a new path to
- * the best of our ability enforcing the group by session.
- *
- * Return the allocated string containing the new stream path or else NULL.
- */
-char *backward_compat_group_by_session(const char *path,
- const char *local_session_name,
- time_t relay_session_creation_time)
-{
- int ret;
- size_t len;
- char *leftover_ptr;
- char *local_copy = NULL;
- char *datetime = NULL;
- char *partial_base_path = NULL;
- char *filepath_per_session = NULL;
- const char *second_token_ptr;
- const char *leftover_second_token_ptr;
- const char *hostname_ptr;
- regex_t regex;
-
- LTTNG_ASSERT(path);
- LTTNG_ASSERT(local_session_name);
- LTTNG_ASSERT(local_session_name[0] != '\0');
-
- DBG("Parsing path \"%s\" of session \"%s\" to create a new path that is grouped by session",
- path, local_session_name);
-
- /* Get a local copy for strtok */
- local_copy = strdup(path);
- if (!local_copy) {
- PERROR("Failed to parse session path: couldn't copy input path");
- goto error;
- }
-
- /*
- * The use of strtok with '/' as delimiter is valid since we refuse '/'
- * in session name and '/' is not a valid hostname character based on
- * RFC-952 [1], RFC-921 [2] and refined in RFC-1123 [3].
- * [1] https://tools.ietf.org/html/rfc952
- * [2] https://tools.ietf.org/html/rfc921
- * [3] https://tools.ietf.org/html/rfc1123#page-13
- */
-
- /*
- * Get the hostname and possible session_name.
- * Note that we can get the hostname and session name from the
- * relay_session object we already have. Still, it is easier to
- * tokenized the passed path to obtain the start of the path leftover.
- */
- hostname_ptr = strtok_r(local_copy, "/", &leftover_ptr);
- if (!hostname_ptr) {
- ERR("Failed to parse session path \"%s\": couldn't identify hostname",
- path);
- goto error;
- }
-
- second_token_ptr = strtok_r(NULL, "/", &leftover_ptr);
- if (!second_token_ptr) {
- ERR("Failed to parse session path \"%s\": couldn't identify session name",
- path);
- goto error;
- }
-
- /*
- * Check if the second token is a base path set at url level. This is
- * legal in streaming, live and snapshot [1]. Otherwise it is the
- * session name with possibly a datetime attached [2]. Note that when
- * "adding" snapshot output (lttng snapshot add-output), no session name
- * is present in the path by default. The handling for "base path" take
- * care of this case as well.
- * [1] e.g --set-url net://localhost/my_marvellous_path
- * [2] Can be:
- * <session_name>
- * When using --snapshot on session create.
- * <session_name>-<date>-<time>
- * <auto>-<date>-<time>
- */
- if (strncmp(second_token_ptr, local_session_name,
- strlen(local_session_name)) != 0) {
- /*
- * Token does not start with session name.
- * This mean this is an extra path scenario.
- * Duplicate the current token since it is part of an
- * base_path.
- * Set secDuplicate the current token since it is part of an
- * base_path. The rest is the leftover.
- * Set second_token_ptr to the local_session_name for further
- * processing.
- */
- partial_base_path = strdup(second_token_ptr);
- if (!partial_base_path) {
- PERROR("Failed to parse session path: couldn't copy partial base path");
- goto error;
- }
-
- second_token_ptr = local_session_name;
- }
-
- /*
- * Based on the previous test, we can move inside the token ptr to
- * remove the "local_session_name" and inspect the rest of the token.
- * We are looking into extracting the creation datetime from either the
- * session_name or the token. We need to to all this gymnastic because
- * an extra path could decide to append a datetime to its first
- * subdirectory.
- * Possible scenario:
- * <session_name>
- * <session_name>-<date>-<time>
- * <auto>-<date>-<time>
- * <session_name>_base_path_foo_bar
- * <session_name>-<false date>-<false-time> (via a base path)
- *
- * We have no way to discern from the basic scenario of:
- * <session_name>-<date>-<time>
- * and one done using a base path with the exact format we normally
- * expect.
- *
- * e.g:
- * lttng create my_session -U
- * net://localhost/my_session-19910319-120000/
- */
- ret = regcomp(®ex, DATETIME_REGEX, 0);
- if (ret) {
- ERR("Failed to parse session path: regex compilation failed with code %d", ret);
- goto error;
- }
-
- leftover_second_token_ptr =
- second_token_ptr + strlen(local_session_name);
- len = strlen(leftover_second_token_ptr);
- if (len == 0) {
- /*
- * We are either dealing with an auto session name or only the
- * session_name. If this is a auto session name, we need to
- * fetch the creation datetime.
- */
- ret = regexec(®ex, local_session_name, 0, NULL, 0);
- if (ret == 0) {
- const ssize_t local_session_name_offset =
- strlen(local_session_name) - DATETIME_STR_LEN + 1;
-
- LTTNG_ASSERT(local_session_name_offset >= 0);
- datetime = strdup(local_session_name +
- local_session_name_offset);
- if (!datetime) {
- PERROR("Failed to parse session path: couldn't copy datetime on regex match");
- goto error_regex;
- }
- } else {
- datetime = zmalloc(DATETIME_STR_LEN);
- if (!datetime) {
- PERROR("Failed to allocate DATETIME string");
- goto error;
- }
-
- ret = time_to_datetime_str(relay_session_creation_time,
- datetime, DATETIME_STR_LEN);
- if (ret) {
- /* time_to_datetime_str already logs errors. */
- goto error;
- }
- }
- } else if (len == DATETIME_STR_LEN &&
- !regexec(®ex, leftover_second_token_ptr, 0, NULL,
- 0)) {
- /*
- * The leftover from the second token is of format
- * "-<datetime>", use it as the creation time.
- * Ignore leading "-".
- */
- datetime = strdup(&leftover_second_token_ptr[1]);
- if (!datetime) {
- PERROR("Failed to parse session path: couldn't copy datetime on regex match");
- goto error_regex;
- }
- } else {
- /*
- * Base path scenario.
- * We cannot try to extract the datetime from the session name
- * since nothing prevent a user to name a session in the
- * "name-<datetime>" format. Using the datetime from such a
- * session would be invalid.
- * */
- LTTNG_ASSERT(partial_base_path == NULL);
- LTTNG_ASSERT(datetime == NULL);
-
- partial_base_path = strdup(second_token_ptr);
- if (!partial_base_path) {
- PERROR("Failed to parse session path: couldn't copy partial base path");
- goto error_regex;
- }
- }
-
- ret = asprintf(&filepath_per_session, "%s/%s%s%s/%s%s%s",
- local_session_name, hostname_ptr, datetime ? "-" : "",
- datetime ? datetime : "",
- partial_base_path ? partial_base_path : "",
- partial_base_path ? "/" : "", leftover_ptr);
- if (ret < 0) {
- filepath_per_session = NULL;
- goto error;
- }
-error_regex:
- regfree(®ex);
-error:
- free(local_copy);
- free(partial_base_path);
- free(datetime);
- return filepath_per_session;
-}
--- /dev/null
+/*
+ * Copyright (C) 2019 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include "common/time.h"
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <common/common.h>
+#include <common/defaults.h>
+#include <common/utils.h>
+
+#include "backward-compatibility-group-by.h"
+
+#define DATETIME_REGEX \
+ ".*-[1-2][0-9][0-9][0-9][0-1][0-9][0-3][0-9]-[0-2][0-9][0-5][0-9][0-5][0-9]$"
+
+/*
+ * Provide support for --group-output-by-session for producer >= 2.4 and < 2.11.
+ * Take the stream path, extract all available information, craft a new path to
+ * the best of our ability enforcing the group by session.
+ *
+ * Return the allocated string containing the new stream path or else NULL.
+ */
+char *backward_compat_group_by_session(const char *path,
+ const char *local_session_name,
+ time_t relay_session_creation_time)
+{
+ int ret;
+ size_t len;
+ char *leftover_ptr;
+ char *local_copy = NULL;
+ char *datetime = NULL;
+ char *partial_base_path = NULL;
+ char *filepath_per_session = NULL;
+ const char *second_token_ptr;
+ const char *leftover_second_token_ptr;
+ const char *hostname_ptr;
+ regex_t regex;
+
+ LTTNG_ASSERT(path);
+ LTTNG_ASSERT(local_session_name);
+ LTTNG_ASSERT(local_session_name[0] != '\0');
+
+ DBG("Parsing path \"%s\" of session \"%s\" to create a new path that is grouped by session",
+ path, local_session_name);
+
+ /* Get a local copy for strtok */
+ local_copy = strdup(path);
+ if (!local_copy) {
+ PERROR("Failed to parse session path: couldn't copy input path");
+ goto error;
+ }
+
+ /*
+ * The use of strtok with '/' as delimiter is valid since we refuse '/'
+ * in session name and '/' is not a valid hostname character based on
+ * RFC-952 [1], RFC-921 [2] and refined in RFC-1123 [3].
+ * [1] https://tools.ietf.org/html/rfc952
+ * [2] https://tools.ietf.org/html/rfc921
+ * [3] https://tools.ietf.org/html/rfc1123#page-13
+ */
+
+ /*
+ * Get the hostname and possible session_name.
+ * Note that we can get the hostname and session name from the
+ * relay_session object we already have. Still, it is easier to
+ * tokenized the passed path to obtain the start of the path leftover.
+ */
+ hostname_ptr = strtok_r(local_copy, "/", &leftover_ptr);
+ if (!hostname_ptr) {
+ ERR("Failed to parse session path \"%s\": couldn't identify hostname",
+ path);
+ goto error;
+ }
+
+ second_token_ptr = strtok_r(NULL, "/", &leftover_ptr);
+ if (!second_token_ptr) {
+ ERR("Failed to parse session path \"%s\": couldn't identify session name",
+ path);
+ goto error;
+ }
+
+ /*
+ * Check if the second token is a base path set at url level. This is
+ * legal in streaming, live and snapshot [1]. Otherwise it is the
+ * session name with possibly a datetime attached [2]. Note that when
+ * "adding" snapshot output (lttng snapshot add-output), no session name
+ * is present in the path by default. The handling for "base path" take
+ * care of this case as well.
+ * [1] e.g --set-url net://localhost/my_marvellous_path
+ * [2] Can be:
+ * <session_name>
+ * When using --snapshot on session create.
+ * <session_name>-<date>-<time>
+ * <auto>-<date>-<time>
+ */
+ if (strncmp(second_token_ptr, local_session_name,
+ strlen(local_session_name)) != 0) {
+ /*
+ * Token does not start with session name.
+ * This mean this is an extra path scenario.
+ * Duplicate the current token since it is part of an
+ * base_path.
+ * Set secDuplicate the current token since it is part of an
+ * base_path. The rest is the leftover.
+ * Set second_token_ptr to the local_session_name for further
+ * processing.
+ */
+ partial_base_path = strdup(second_token_ptr);
+ if (!partial_base_path) {
+ PERROR("Failed to parse session path: couldn't copy partial base path");
+ goto error;
+ }
+
+ second_token_ptr = local_session_name;
+ }
+
+ /*
+ * Based on the previous test, we can move inside the token ptr to
+ * remove the "local_session_name" and inspect the rest of the token.
+ * We are looking into extracting the creation datetime from either the
+ * session_name or the token. We need to to all this gymnastic because
+ * an extra path could decide to append a datetime to its first
+ * subdirectory.
+ * Possible scenario:
+ * <session_name>
+ * <session_name>-<date>-<time>
+ * <auto>-<date>-<time>
+ * <session_name>_base_path_foo_bar
+ * <session_name>-<false date>-<false-time> (via a base path)
+ *
+ * We have no way to discern from the basic scenario of:
+ * <session_name>-<date>-<time>
+ * and one done using a base path with the exact format we normally
+ * expect.
+ *
+ * e.g:
+ * lttng create my_session -U
+ * net://localhost/my_session-19910319-120000/
+ */
+ ret = regcomp(®ex, DATETIME_REGEX, 0);
+ if (ret) {
+ ERR("Failed to parse session path: regex compilation failed with code %d", ret);
+ goto error;
+ }
+
+ leftover_second_token_ptr =
+ second_token_ptr + strlen(local_session_name);
+ len = strlen(leftover_second_token_ptr);
+ if (len == 0) {
+ /*
+ * We are either dealing with an auto session name or only the
+ * session_name. If this is a auto session name, we need to
+ * fetch the creation datetime.
+ */
+ ret = regexec(®ex, local_session_name, 0, NULL, 0);
+ if (ret == 0) {
+ const ssize_t local_session_name_offset =
+ strlen(local_session_name) - DATETIME_STR_LEN + 1;
+
+ LTTNG_ASSERT(local_session_name_offset >= 0);
+ datetime = strdup(local_session_name +
+ local_session_name_offset);
+ if (!datetime) {
+ PERROR("Failed to parse session path: couldn't copy datetime on regex match");
+ goto error_regex;
+ }
+ } else {
+ datetime = (char *) zmalloc(DATETIME_STR_LEN);
+ if (!datetime) {
+ PERROR("Failed to allocate DATETIME string");
+ goto error;
+ }
+
+ ret = time_to_datetime_str(relay_session_creation_time,
+ datetime, DATETIME_STR_LEN);
+ if (ret) {
+ /* time_to_datetime_str already logs errors. */
+ goto error;
+ }
+ }
+ } else if (len == DATETIME_STR_LEN &&
+ !regexec(®ex, leftover_second_token_ptr, 0, NULL,
+ 0)) {
+ /*
+ * The leftover from the second token is of format
+ * "-<datetime>", use it as the creation time.
+ * Ignore leading "-".
+ */
+ datetime = strdup(&leftover_second_token_ptr[1]);
+ if (!datetime) {
+ PERROR("Failed to parse session path: couldn't copy datetime on regex match");
+ goto error_regex;
+ }
+ } else {
+ /*
+ * Base path scenario.
+ * We cannot try to extract the datetime from the session name
+ * since nothing prevent a user to name a session in the
+ * "name-<datetime>" format. Using the datetime from such a
+ * session would be invalid.
+ * */
+ LTTNG_ASSERT(partial_base_path == NULL);
+ LTTNG_ASSERT(datetime == NULL);
+
+ partial_base_path = strdup(second_token_ptr);
+ if (!partial_base_path) {
+ PERROR("Failed to parse session path: couldn't copy partial base path");
+ goto error_regex;
+ }
+ }
+
+ ret = asprintf(&filepath_per_session, "%s/%s%s%s/%s%s%s",
+ local_session_name, hostname_ptr, datetime ? "-" : "",
+ datetime ? datetime : "",
+ partial_base_path ? partial_base_path : "",
+ partial_base_path ? "/" : "", leftover_ptr);
+ if (ret < 0) {
+ filepath_per_session = NULL;
+ goto error;
+ }
+error_regex:
+ regfree(®ex);
+error:
+ free(local_copy);
+ free(partial_base_path);
+ free(datetime);
+ return filepath_per_session;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-
-#include <common/common.h>
-#include <common/sessiond-comm/relayd.h>
-#include <common/compat/string.h>
-#include <lttng/constant.h>
-
-#include "cmd-2-1.h"
-#include "utils.h"
-
-/*
- * cmd_recv_stream_2_1 allocates path_name and channel_name.
- */
-int cmd_recv_stream_2_1(const struct lttng_buffer_view *payload,
- char **ret_path_name, char **ret_channel_name)
-{
- int ret;
- struct lttcomm_relayd_add_stream stream_info;
- char *path_name = NULL;
- char *channel_name = NULL;
- size_t len;
-
- if (payload->size < sizeof(stream_info)) {
- ERR("Unexpected payload size in \"cmd_recv_stream_2_1\": expected >= %zu bytes, got %zu bytes",
- sizeof(stream_info), payload->size);
- ret = -1;
- goto error;
- }
- memcpy(&stream_info, payload->data, sizeof(stream_info));
-
- len = lttng_strnlen(stream_info.pathname, sizeof(stream_info.pathname));
- /* Ensure that NULL-terminated and fits in local filename length. */
- if (len == sizeof(stream_info.pathname) || len >= LTTNG_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Path name too long");
- goto error;
- }
- path_name = strdup(stream_info.pathname);
- if (!path_name) {
- PERROR("Path name allocation");
- ret = -ENOMEM;
- goto error;
- }
- len = lttng_strnlen(stream_info.channel_name, sizeof(stream_info.channel_name));
- if (len == sizeof(stream_info.channel_name) || len >= DEFAULT_STREAM_NAME_LEN) {
- ret = -ENAMETOOLONG;
- ERR("Channel name too long");
- goto error;
- }
- channel_name = strdup(stream_info.channel_name);
- if (!channel_name) {
- ret = -errno;
- PERROR("Channel name allocation");
- goto error;
- }
-
- *ret_path_name = path_name;
- *ret_channel_name = channel_name;
- return 0;
-error:
- free(path_name);
- free(channel_name);
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+
+#include <common/common.h>
+#include <common/sessiond-comm/relayd.h>
+#include <common/compat/string.h>
+#include <lttng/constant.h>
+
+#include "cmd-2-1.h"
+#include "utils.h"
+
+/*
+ * cmd_recv_stream_2_1 allocates path_name and channel_name.
+ */
+int cmd_recv_stream_2_1(const struct lttng_buffer_view *payload,
+ char **ret_path_name, char **ret_channel_name)
+{
+ int ret;
+ struct lttcomm_relayd_add_stream stream_info;
+ char *path_name = NULL;
+ char *channel_name = NULL;
+ size_t len;
+
+ if (payload->size < sizeof(stream_info)) {
+ ERR("Unexpected payload size in \"cmd_recv_stream_2_1\": expected >= %zu bytes, got %zu bytes",
+ sizeof(stream_info), payload->size);
+ ret = -1;
+ goto error;
+ }
+ memcpy(&stream_info, payload->data, sizeof(stream_info));
+
+ len = lttng_strnlen(stream_info.pathname, sizeof(stream_info.pathname));
+ /* Ensure that NULL-terminated and fits in local filename length. */
+ if (len == sizeof(stream_info.pathname) || len >= LTTNG_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Path name too long");
+ goto error;
+ }
+ path_name = strdup(stream_info.pathname);
+ if (!path_name) {
+ PERROR("Path name allocation");
+ ret = -ENOMEM;
+ goto error;
+ }
+ len = lttng_strnlen(stream_info.channel_name, sizeof(stream_info.channel_name));
+ if (len == sizeof(stream_info.channel_name) || len >= DEFAULT_STREAM_NAME_LEN) {
+ ret = -ENAMETOOLONG;
+ ERR("Channel name too long");
+ goto error;
+ }
+ channel_name = strdup(stream_info.channel_name);
+ if (!channel_name) {
+ ret = -errno;
+ PERROR("Channel name allocation");
+ goto error;
+ }
+
+ *ret_path_name = path_name;
+ *ret_channel_name = channel_name;
+ return 0;
+error:
+ free(path_name);
+ free(channel_name);
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2018 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <inttypes.h>
-
-#include <common/common.h>
-#include <common/sessiond-comm/relayd.h>
-
-#include <common/compat/endian.h>
-#include <common/compat/string.h>
-#include <lttng/constant.h>
-
-#include "cmd-2-11.h"
-#include "utils.h"
-
-int cmd_create_session_2_11(const struct lttng_buffer_view *payload,
- char *session_name, char *hostname, char *base_path,
- uint32_t *live_timer, bool *snapshot,
- uint64_t *id_sessiond, lttng_uuid sessiond_uuid,
- bool *has_current_chunk, uint64_t *current_chunk_id,
- time_t *creation_time,
- bool *session_name_contains_creation_time)
-{
- int ret;
- struct lttcomm_relayd_create_session_2_11 header;
- size_t header_len, received_names_size, offset;
- struct lttng_buffer_view session_name_view;
- struct lttng_buffer_view hostname_view;
- struct lttng_buffer_view base_path_view;
-
- header_len = sizeof(header);
-
- if (payload->size < header_len) {
- ERR("Unexpected payload size in \"cmd_create_session_2_11\": expected >= %zu bytes, got %zu bytes",
- header_len, payload->size);
- ret = -1;
- goto error;
- }
- memcpy(&header, payload->data, header_len);
-
- header.session_name_len = be32toh(header.session_name_len);
- header.hostname_len = be32toh(header.hostname_len);
- header.base_path_len = be32toh(header.base_path_len);
- header.live_timer = be32toh(header.live_timer);
- header.current_chunk_id.value = be64toh(header.current_chunk_id.value);
- header.current_chunk_id.is_set = !!header.current_chunk_id.is_set;
- header.creation_time = be64toh(header.creation_time);
-
- lttng_uuid_copy(sessiond_uuid, header.sessiond_uuid);
-
- received_names_size = header.session_name_len + header.hostname_len +
- header.base_path_len;
- if (payload->size < header_len + received_names_size) {
- ERR("Unexpected payload size in \"cmd_create_session_2_11\": expected >= %zu bytes, got %zu bytes",
- header_len + received_names_size, payload->size);
- ret = -1;
- goto error;
- }
-
- /* Validate length against defined constant. */
- if (header.session_name_len > LTTNG_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Length of session name (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.session_name_len, LTTNG_NAME_MAX);
- goto error;
- } else if (header.session_name_len == 0) {
- ret = -EINVAL;
- ERR("Illegal session name length of 0 received");
- goto error;
- }
- if (header.hostname_len > LTTNG_HOST_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Length of hostname (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.hostname_len, LTTNG_HOST_NAME_MAX);
- goto error;
- }
- if (header.base_path_len > LTTNG_PATH_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Length of base_path (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.base_path_len, PATH_MAX);
- goto error;
- }
-
- offset = header_len;
- session_name_view = lttng_buffer_view_from_view(payload, offset,
- header.session_name_len);
- if (!lttng_buffer_view_is_valid(&session_name_view)) {
- ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain session name");
- ret = -1;
- goto error;
- }
-
- offset += header.session_name_len;
- hostname_view = lttng_buffer_view_from_view(payload,
- offset, header.hostname_len);
- if (!lttng_buffer_view_is_valid(&hostname_view)) {
- ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain hostname");
- ret = -1;
- goto error;
- }
-
- offset += header.hostname_len;
- base_path_view = lttng_buffer_view_from_view(payload,
- offset, header.base_path_len);
- if (header.base_path_len > 0 && !lttng_buffer_view_is_valid(&base_path_view)) {
- ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain base path");
- ret = -1;
- goto error;
- }
-
- /* Validate that names are NULL terminated. */
- if (session_name_view.data[session_name_view.size - 1] != '\0') {
- ERR("cmd_create_session_2_11 session_name is invalid (not NULL terminated)");
- ret = -1;
- goto error;
- }
-
- if (hostname_view.data[hostname_view.size - 1] != '\0') {
- ERR("cmd_create_session_2_11 hostname is invalid (not NULL terminated)");
- ret = -1;
- goto error;
- }
-
- if (base_path_view.size != 0 &&
- base_path_view.data[base_path_view.size - 1] != '\0') {
- ERR("cmd_create_session_2_11 base_path is invalid (not NULL terminated)");
- ret = -1;
- goto error;
- }
-
- /*
- * Length and null-termination check are already performed.
- * LTTNG_NAME_MAX, LTTNG_HOST_NAME_MAX, and LTTNG_PATH_MAX max sizes are expected.
- */
- strcpy(session_name, session_name_view.data);
- strcpy(hostname, hostname_view.data);
- strcpy(base_path, base_path_view.size ? base_path_view.data : "");
-
- *live_timer = header.live_timer;
- *snapshot = !!header.snapshot;
- *current_chunk_id = header.current_chunk_id.value;
- *has_current_chunk = header.current_chunk_id.is_set;
- *creation_time = (time_t) header.creation_time;
- *session_name_contains_creation_time =
- header.session_name_contains_creation_time;
-
- ret = 0;
-
-error:
- return ret;
-}
-
-/*
- * cmd_recv_stream_2_11 allocates path_name and channel_name.
- */
-int cmd_recv_stream_2_11(const struct lttng_buffer_view *payload,
- char **ret_path_name, char **ret_channel_name,
- uint64_t *tracefile_size, uint64_t *tracefile_count,
- uint64_t *trace_archive_id)
-{
- int ret;
- struct lttcomm_relayd_add_stream_2_11 header;
- size_t header_len, received_names_size;
- struct lttng_buffer_view channel_name_view;
- struct lttng_buffer_view pathname_view;
- char *path_name = NULL;
- char *channel_name = NULL;
-
- header_len = sizeof(header);
-
- if (payload->size < header_len) {
- ERR("Unexpected payload size in \"cmd_recv_stream_2_11\": expected >= %zu bytes, got %zu bytes",
- header_len, payload->size);
- ret = -1;
- goto error;
- }
- memcpy(&header, payload->data, header_len);
-
- header.channel_name_len = be32toh(header.channel_name_len);
- header.pathname_len = be32toh(header.pathname_len);
- header.tracefile_size = be64toh(header.tracefile_size);
- header.tracefile_count = be64toh(header.tracefile_count);
- header.trace_chunk_id = be64toh(header.trace_chunk_id);
-
- received_names_size = header.channel_name_len + header.pathname_len;
- if (payload->size < header_len + received_names_size) {
- ERR("Unexpected payload size in \"cmd_recv_stream_2_11\": expected >= %zu bytes, got %zu bytes",
- header_len + received_names_size, payload->size);
- ret = -1;
- goto error;
- }
-
- /* Validate length against defined constant. */
- if (header.channel_name_len > DEFAULT_STREAM_NAME_LEN) {
- ret = -ENAMETOOLONG;
- ERR("Channel name too long");
- goto error;
- }
- if (header.pathname_len > LTTNG_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Pathname too long");
- goto error;
- }
-
- /* Validate that names are (NULL terminated. */
- channel_name_view = lttng_buffer_view_from_view(payload, header_len,
- header.channel_name_len);
- if (!lttng_buffer_view_is_valid(&channel_name_view)) {
- ERR("Invalid payload received in \"cmd_recv_stream_2_11\": buffer too short for channel name");
- ret = -1;
- goto error;
- }
-
- if (channel_name_view.data[channel_name_view.size - 1] != '\0') {
- ERR("cmd_recv_stream_2_11 channel_name is invalid (not NULL terminated)");
- ret = -1;
- goto error;
- }
-
- pathname_view = lttng_buffer_view_from_view(payload,
- header_len + header.channel_name_len, header.pathname_len);
- if (!lttng_buffer_view_is_valid(&pathname_view)) {
- ERR("Invalid payload received in \"cmd_recv_stream_2_11\": buffer too short for path name");
- ret = -1;
- goto error;
- }
-
- if (pathname_view.data[pathname_view.size - 1] != '\0') {
- ERR("cmd_recv_stream_2_11 patname is invalid (not NULL terminated)");
- ret = -1;
- goto error;
- }
-
- channel_name = strdup(channel_name_view.data);
- if (!channel_name) {
- ret = -errno;
- PERROR("Channel name allocation");
- goto error;
- }
-
- path_name = strdup(pathname_view.data);
- if (!path_name) {
- PERROR("Path name allocation");
- ret = -ENOMEM;
- goto error;
- }
-
- *tracefile_size = header.tracefile_size;
- *tracefile_count = header.tracefile_count;
- *trace_archive_id = header.trace_chunk_id;
- *ret_path_name = path_name;
- *ret_channel_name = channel_name;
- /* Move ownership to caller */
- path_name = NULL;
- channel_name = NULL;
- ret = 0;
-error:
- free(channel_name);
- free(path_name);
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2018 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <inttypes.h>
+
+#include <common/common.h>
+#include <common/sessiond-comm/relayd.h>
+
+#include <common/compat/endian.h>
+#include <common/compat/string.h>
+#include <lttng/constant.h>
+
+#include "cmd-2-11.h"
+#include "utils.h"
+
+int cmd_create_session_2_11(const struct lttng_buffer_view *payload,
+ char *session_name, char *hostname, char *base_path,
+ uint32_t *live_timer, bool *snapshot,
+ uint64_t *id_sessiond, lttng_uuid sessiond_uuid,
+ bool *has_current_chunk, uint64_t *current_chunk_id,
+ time_t *creation_time,
+ bool *session_name_contains_creation_time)
+{
+ int ret;
+ struct lttcomm_relayd_create_session_2_11 header;
+ size_t header_len, received_names_size, offset;
+ struct lttng_buffer_view session_name_view;
+ struct lttng_buffer_view hostname_view;
+ struct lttng_buffer_view base_path_view;
+
+ header_len = sizeof(header);
+
+ if (payload->size < header_len) {
+ ERR("Unexpected payload size in \"cmd_create_session_2_11\": expected >= %zu bytes, got %zu bytes",
+ header_len, payload->size);
+ ret = -1;
+ goto error;
+ }
+ memcpy(&header, payload->data, header_len);
+
+ header.session_name_len = be32toh(header.session_name_len);
+ header.hostname_len = be32toh(header.hostname_len);
+ header.base_path_len = be32toh(header.base_path_len);
+ header.live_timer = be32toh(header.live_timer);
+ header.current_chunk_id.value = be64toh(header.current_chunk_id.value);
+ header.current_chunk_id.is_set = !!header.current_chunk_id.is_set;
+ header.creation_time = be64toh(header.creation_time);
+
+ lttng_uuid_copy(sessiond_uuid, header.sessiond_uuid);
+
+ received_names_size = header.session_name_len + header.hostname_len +
+ header.base_path_len;
+ if (payload->size < header_len + received_names_size) {
+ ERR("Unexpected payload size in \"cmd_create_session_2_11\": expected >= %zu bytes, got %zu bytes",
+ header_len + received_names_size, payload->size);
+ ret = -1;
+ goto error;
+ }
+
+ /* Validate length against defined constant. */
+ if (header.session_name_len > LTTNG_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Length of session name (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.session_name_len, LTTNG_NAME_MAX);
+ goto error;
+ } else if (header.session_name_len == 0) {
+ ret = -EINVAL;
+ ERR("Illegal session name length of 0 received");
+ goto error;
+ }
+ if (header.hostname_len > LTTNG_HOST_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Length of hostname (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.hostname_len, LTTNG_HOST_NAME_MAX);
+ goto error;
+ }
+ if (header.base_path_len > LTTNG_PATH_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Length of base_path (%" PRIu32 " bytes) received in create_session command exceeds maximum length (%d bytes)", header.base_path_len, PATH_MAX);
+ goto error;
+ }
+
+ offset = header_len;
+ session_name_view = lttng_buffer_view_from_view(payload, offset,
+ header.session_name_len);
+ if (!lttng_buffer_view_is_valid(&session_name_view)) {
+ ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain session name");
+ ret = -1;
+ goto error;
+ }
+
+ offset += header.session_name_len;
+ hostname_view = lttng_buffer_view_from_view(payload,
+ offset, header.hostname_len);
+ if (!lttng_buffer_view_is_valid(&hostname_view)) {
+ ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain hostname");
+ ret = -1;
+ goto error;
+ }
+
+ offset += header.hostname_len;
+ base_path_view = lttng_buffer_view_from_view(payload,
+ offset, header.base_path_len);
+ if (header.base_path_len > 0 && !lttng_buffer_view_is_valid(&base_path_view)) {
+ ERR("Invalid payload in \"cmd_create_session_2_11\": buffer too short to contain base path");
+ ret = -1;
+ goto error;
+ }
+
+ /* Validate that names are NULL terminated. */
+ if (session_name_view.data[session_name_view.size - 1] != '\0') {
+ ERR("cmd_create_session_2_11 session_name is invalid (not NULL terminated)");
+ ret = -1;
+ goto error;
+ }
+
+ if (hostname_view.data[hostname_view.size - 1] != '\0') {
+ ERR("cmd_create_session_2_11 hostname is invalid (not NULL terminated)");
+ ret = -1;
+ goto error;
+ }
+
+ if (base_path_view.size != 0 &&
+ base_path_view.data[base_path_view.size - 1] != '\0') {
+ ERR("cmd_create_session_2_11 base_path is invalid (not NULL terminated)");
+ ret = -1;
+ goto error;
+ }
+
+ /*
+ * Length and null-termination check are already performed.
+ * LTTNG_NAME_MAX, LTTNG_HOST_NAME_MAX, and LTTNG_PATH_MAX max sizes are expected.
+ */
+ strcpy(session_name, session_name_view.data);
+ strcpy(hostname, hostname_view.data);
+ strcpy(base_path, base_path_view.size ? base_path_view.data : "");
+
+ *live_timer = header.live_timer;
+ *snapshot = !!header.snapshot;
+ *current_chunk_id = header.current_chunk_id.value;
+ *has_current_chunk = header.current_chunk_id.is_set;
+ *creation_time = (time_t) header.creation_time;
+ *session_name_contains_creation_time =
+ header.session_name_contains_creation_time;
+
+ ret = 0;
+
+error:
+ return ret;
+}
+
+/*
+ * cmd_recv_stream_2_11 allocates path_name and channel_name.
+ */
+int cmd_recv_stream_2_11(const struct lttng_buffer_view *payload,
+ char **ret_path_name, char **ret_channel_name,
+ uint64_t *tracefile_size, uint64_t *tracefile_count,
+ uint64_t *trace_archive_id)
+{
+ int ret;
+ struct lttcomm_relayd_add_stream_2_11 header;
+ size_t header_len, received_names_size;
+ struct lttng_buffer_view channel_name_view;
+ struct lttng_buffer_view pathname_view;
+ char *path_name = NULL;
+ char *channel_name = NULL;
+
+ header_len = sizeof(header);
+
+ if (payload->size < header_len) {
+ ERR("Unexpected payload size in \"cmd_recv_stream_2_11\": expected >= %zu bytes, got %zu bytes",
+ header_len, payload->size);
+ ret = -1;
+ goto error;
+ }
+ memcpy(&header, payload->data, header_len);
+
+ header.channel_name_len = be32toh(header.channel_name_len);
+ header.pathname_len = be32toh(header.pathname_len);
+ header.tracefile_size = be64toh(header.tracefile_size);
+ header.tracefile_count = be64toh(header.tracefile_count);
+ header.trace_chunk_id = be64toh(header.trace_chunk_id);
+
+ received_names_size = header.channel_name_len + header.pathname_len;
+ if (payload->size < header_len + received_names_size) {
+ ERR("Unexpected payload size in \"cmd_recv_stream_2_11\": expected >= %zu bytes, got %zu bytes",
+ header_len + received_names_size, payload->size);
+ ret = -1;
+ goto error;
+ }
+
+ /* Validate length against defined constant. */
+ if (header.channel_name_len > DEFAULT_STREAM_NAME_LEN) {
+ ret = -ENAMETOOLONG;
+ ERR("Channel name too long");
+ goto error;
+ }
+ if (header.pathname_len > LTTNG_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Pathname too long");
+ goto error;
+ }
+
+ /* Validate that names are (NULL terminated. */
+ channel_name_view = lttng_buffer_view_from_view(payload, header_len,
+ header.channel_name_len);
+ if (!lttng_buffer_view_is_valid(&channel_name_view)) {
+ ERR("Invalid payload received in \"cmd_recv_stream_2_11\": buffer too short for channel name");
+ ret = -1;
+ goto error;
+ }
+
+ if (channel_name_view.data[channel_name_view.size - 1] != '\0') {
+ ERR("cmd_recv_stream_2_11 channel_name is invalid (not NULL terminated)");
+ ret = -1;
+ goto error;
+ }
+
+ pathname_view = lttng_buffer_view_from_view(payload,
+ header_len + header.channel_name_len, header.pathname_len);
+ if (!lttng_buffer_view_is_valid(&pathname_view)) {
+ ERR("Invalid payload received in \"cmd_recv_stream_2_11\": buffer too short for path name");
+ ret = -1;
+ goto error;
+ }
+
+ if (pathname_view.data[pathname_view.size - 1] != '\0') {
+ ERR("cmd_recv_stream_2_11 patname is invalid (not NULL terminated)");
+ ret = -1;
+ goto error;
+ }
+
+ channel_name = strdup(channel_name_view.data);
+ if (!channel_name) {
+ ret = -errno;
+ PERROR("Channel name allocation");
+ goto error;
+ }
+
+ path_name = strdup(pathname_view.data);
+ if (!path_name) {
+ PERROR("Path name allocation");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ *tracefile_size = header.tracefile_size;
+ *tracefile_count = header.tracefile_count;
+ *trace_archive_id = header.trace_chunk_id;
+ *ret_path_name = path_name;
+ *ret_channel_name = channel_name;
+ /* Move ownership to caller */
+ path_name = NULL;
+ channel_name = NULL;
+ ret = 0;
+error:
+ free(channel_name);
+ free(path_name);
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-
-#include <common/common.h>
-#include <common/sessiond-comm/relayd.h>
-
-#include <common/compat/endian.h>
-#include <common/compat/string.h>
-#include <lttng/constant.h>
-
-#include "cmd-2-2.h"
-#include "cmd-2-1.h"
-#include "utils.h"
-
-/*
- * cmd_recv_stream_2_2 allocates path_name and channel_name.
- */
-int cmd_recv_stream_2_2(const struct lttng_buffer_view *payload,
- char **ret_path_name, char **ret_channel_name,
- uint64_t *tracefile_size, uint64_t *tracefile_count)
-{
- int ret;
- struct lttcomm_relayd_add_stream_2_2 stream_info;
- char *path_name = NULL;
- char *channel_name = NULL;
- size_t len;
-
- if (payload->size < sizeof(stream_info)) {
- ERR("Unexpected payload size in \"cmd_recv_stream_2_2\": expected >= %zu bytes, got %zu bytes",
- sizeof(stream_info), payload->size);
- ret = -1;
- goto error;
- }
- memcpy(&stream_info, payload->data, sizeof(stream_info));
-
- len = lttng_strnlen(stream_info.pathname, sizeof(stream_info.pathname));
- /* Ensure that NULL-terminated and fits in local filename length. */
- if (len == sizeof(stream_info.pathname) || len >= LTTNG_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Path name too long");
- goto error;
- }
- path_name = strdup(stream_info.pathname);
- if (!path_name) {
- PERROR("Path name allocation");
- ret = -ENOMEM;
- goto error;
- }
- len = lttng_strnlen(stream_info.channel_name, sizeof(stream_info.channel_name));
- if (len == sizeof(stream_info.channel_name) || len >= DEFAULT_STREAM_NAME_LEN) {
- ret = -ENAMETOOLONG;
- ERR("Channel name too long");
- goto error;
- }
- channel_name = strdup(stream_info.channel_name);
- if (!channel_name) {
- ret = -errno;
- PERROR("Channel name allocation");
- goto error;
- }
-
- *tracefile_size = be64toh(stream_info.tracefile_size);
- *tracefile_count = be64toh(stream_info.tracefile_count);
- *ret_path_name = path_name;
- *ret_channel_name = channel_name;
- return 0;
-error:
- free(path_name);
- free(channel_name);
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+
+#include <common/common.h>
+#include <common/sessiond-comm/relayd.h>
+
+#include <common/compat/endian.h>
+#include <common/compat/string.h>
+#include <lttng/constant.h>
+
+#include "cmd-2-2.h"
+#include "cmd-2-1.h"
+#include "utils.h"
+
+/*
+ * cmd_recv_stream_2_2 allocates path_name and channel_name.
+ */
+int cmd_recv_stream_2_2(const struct lttng_buffer_view *payload,
+ char **ret_path_name, char **ret_channel_name,
+ uint64_t *tracefile_size, uint64_t *tracefile_count)
+{
+ int ret;
+ struct lttcomm_relayd_add_stream_2_2 stream_info;
+ char *path_name = NULL;
+ char *channel_name = NULL;
+ size_t len;
+
+ if (payload->size < sizeof(stream_info)) {
+ ERR("Unexpected payload size in \"cmd_recv_stream_2_2\": expected >= %zu bytes, got %zu bytes",
+ sizeof(stream_info), payload->size);
+ ret = -1;
+ goto error;
+ }
+ memcpy(&stream_info, payload->data, sizeof(stream_info));
+
+ len = lttng_strnlen(stream_info.pathname, sizeof(stream_info.pathname));
+ /* Ensure that NULL-terminated and fits in local filename length. */
+ if (len == sizeof(stream_info.pathname) || len >= LTTNG_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Path name too long");
+ goto error;
+ }
+ path_name = strdup(stream_info.pathname);
+ if (!path_name) {
+ PERROR("Path name allocation");
+ ret = -ENOMEM;
+ goto error;
+ }
+ len = lttng_strnlen(stream_info.channel_name, sizeof(stream_info.channel_name));
+ if (len == sizeof(stream_info.channel_name) || len >= DEFAULT_STREAM_NAME_LEN) {
+ ret = -ENAMETOOLONG;
+ ERR("Channel name too long");
+ goto error;
+ }
+ channel_name = strdup(stream_info.channel_name);
+ if (!channel_name) {
+ ret = -errno;
+ PERROR("Channel name allocation");
+ goto error;
+ }
+
+ *tracefile_size = be64toh(stream_info.tracefile_size);
+ *tracefile_count = be64toh(stream_info.tracefile_count);
+ *ret_path_name = path_name;
+ *ret_channel_name = channel_name;
+ return 0;
+error:
+ free(path_name);
+ free(channel_name);
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-
-#include <common/common.h>
-#include <common/sessiond-comm/relayd.h>
-
-#include <common/compat/endian.h>
-#include <common/compat/string.h>
-#include <lttng/constant.h>
-
-#include "cmd-2-4.h"
-#include "lttng-relayd.h"
-
-int cmd_create_session_2_4(const struct lttng_buffer_view *payload,
- char *session_name, char *hostname,
- uint32_t *live_timer, bool *snapshot)
-{
- int ret;
- struct lttcomm_relayd_create_session_2_4 session_info;
- size_t len;
-
- if (payload->size < sizeof(session_info)) {
- ERR("Unexpected payload size in \"cmd_create_session_2_4\": expected >= %zu bytes, got %zu bytes",
- sizeof(session_info), payload->size);
- ret = -1;
- goto error;
- }
- memcpy(&session_info, payload->data, sizeof(session_info));
-
- len = lttng_strnlen(session_info.session_name, sizeof(session_info.session_name));
- /* Ensure that NULL-terminated and fits in local filename length. */
- if (len == sizeof(session_info.session_name) || len >= LTTNG_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Session name too long");
- goto error;
- } else if (len == 0) {
- ret = -EINVAL;
- ERR("Session name can't be of length 0");
- goto error;
- }
- strncpy(session_name, session_info.session_name, LTTNG_NAME_MAX);
-
- len = lttng_strnlen(session_info.hostname, sizeof(session_info.hostname));
- if (len == sizeof(session_info.hostname) || len >= LTTNG_HOST_NAME_MAX) {
- ret = -ENAMETOOLONG;
- ERR("Session name too long");
- goto error;
- }
- strncpy(hostname, session_info.hostname, LTTNG_HOST_NAME_MAX);
-
- *live_timer = be32toh(session_info.live_timer);
- *snapshot = be32toh(session_info.snapshot);
-
- ret = 0;
-
-error:
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+
+#include <common/common.h>
+#include <common/sessiond-comm/relayd.h>
+
+#include <common/compat/endian.h>
+#include <common/compat/string.h>
+#include <lttng/constant.h>
+
+#include "cmd-2-4.h"
+#include "lttng-relayd.h"
+
+int cmd_create_session_2_4(const struct lttng_buffer_view *payload,
+ char *session_name, char *hostname,
+ uint32_t *live_timer, bool *snapshot)
+{
+ int ret;
+ struct lttcomm_relayd_create_session_2_4 session_info;
+ size_t len;
+
+ if (payload->size < sizeof(session_info)) {
+ ERR("Unexpected payload size in \"cmd_create_session_2_4\": expected >= %zu bytes, got %zu bytes",
+ sizeof(session_info), payload->size);
+ ret = -1;
+ goto error;
+ }
+ memcpy(&session_info, payload->data, sizeof(session_info));
+
+ len = lttng_strnlen(session_info.session_name, sizeof(session_info.session_name));
+ /* Ensure that NULL-terminated and fits in local filename length. */
+ if (len == sizeof(session_info.session_name) || len >= LTTNG_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Session name too long");
+ goto error;
+ } else if (len == 0) {
+ ret = -EINVAL;
+ ERR("Session name can't be of length 0");
+ goto error;
+ }
+ strncpy(session_name, session_info.session_name, LTTNG_NAME_MAX);
+
+ len = lttng_strnlen(session_info.hostname, sizeof(session_info.hostname));
+ if (len == sizeof(session_info.hostname) || len >= LTTNG_HOST_NAME_MAX) {
+ ret = -ENAMETOOLONG;
+ ERR("Session name too long");
+ goto error;
+ }
+ strncpy(hostname, session_info.hostname, LTTNG_HOST_NAME_MAX);
+
+ *live_timer = be32toh(session_info.live_timer);
+ *snapshot = be32toh(session_info.snapshot);
+
+ ret = 0;
+
+error:
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <urcu/rculist.h>
-
-#include "connection.h"
-#include "stream.h"
-#include "viewer-session.h"
-
-bool connection_get(struct relay_connection *conn)
-{
- return urcu_ref_get_unless_zero(&conn->ref);
-}
-
-struct relay_connection *connection_get_by_sock(struct lttng_ht *relay_connections_ht,
- int sock)
-{
- struct lttng_ht_node_ulong *node;
- struct lttng_ht_iter iter;
- struct relay_connection *conn = NULL;
-
- LTTNG_ASSERT(sock >= 0);
-
- rcu_read_lock();
- lttng_ht_lookup(relay_connections_ht, (void *)((unsigned long) sock),
- &iter);
- node = lttng_ht_iter_get_node_ulong(&iter);
- if (!node) {
- DBG2("Relay connection by sock %d not found", sock);
- goto end;
- }
- conn = caa_container_of(node, struct relay_connection, sock_n);
- if (!connection_get(conn)) {
- conn = NULL;
- }
-end:
- rcu_read_unlock();
- return conn;
-}
-
-int connection_reset_protocol_state(struct relay_connection *connection)
-{
- int ret = 0;
-
- switch (connection->type) {
- case RELAY_DATA:
- connection->protocol.data.state_id =
- DATA_CONNECTION_STATE_RECEIVE_HEADER;
- memset(&connection->protocol.data.state.receive_header,
- 0,
- sizeof(connection->protocol.data.state.receive_header));
- connection->protocol.data.state.receive_header.left_to_receive =
- sizeof(struct lttcomm_relayd_data_hdr);
- break;
- case RELAY_CONTROL:
- connection->protocol.ctrl.state_id =
- CTRL_CONNECTION_STATE_RECEIVE_HEADER;
- memset(&connection->protocol.ctrl.state.receive_header,
- 0,
- sizeof(connection->protocol.ctrl.state.receive_header));
- connection->protocol.data.state.receive_header.left_to_receive =
- sizeof(struct lttcomm_relayd_hdr);
- ret = lttng_dynamic_buffer_set_size(
- &connection->protocol.ctrl.reception_buffer,
- sizeof(struct lttcomm_relayd_hdr));
- if (ret) {
- ERR("Failed to reinitialize control connection reception buffer size to %zu bytes.", sizeof(struct lttcomm_relayd_hdr));
- goto end;
- }
- break;
- default:
- goto end;
- }
- DBG("Reset communication state of relay connection (fd = %i)",
- connection->sock->fd);
-end:
- return ret;
-}
-
-struct relay_connection *connection_create(struct lttcomm_sock *sock,
- enum connection_type type)
-{
- struct relay_connection *conn;
-
- conn = zmalloc(sizeof(*conn));
- if (!conn) {
- PERROR("zmalloc relay connection");
- goto end;
- }
- urcu_ref_init(&conn->ref);
- conn->type = type;
- conn->sock = sock;
- lttng_ht_node_init_ulong(&conn->sock_n, (unsigned long) conn->sock->fd);
- if (conn->type == RELAY_CONTROL) {
- lttng_dynamic_buffer_init(&conn->protocol.ctrl.reception_buffer);
- }
- connection_reset_protocol_state(conn);
-end:
- return conn;
-}
-
-static void rcu_free_connection(struct rcu_head *head)
-{
- struct relay_connection *conn =
- caa_container_of(head, struct relay_connection, rcu_node);
-
- lttcomm_destroy_sock(conn->sock);
- if (conn->viewer_session) {
- viewer_session_destroy(conn->viewer_session);
- conn->viewer_session = NULL;
- }
- if (conn->type == RELAY_CONTROL) {
- lttng_dynamic_buffer_reset(
- &conn->protocol.ctrl.reception_buffer);
- }
- free(conn);
-}
-
-static void destroy_connection(struct relay_connection *conn)
-{
- call_rcu(&conn->rcu_node, rcu_free_connection);
-}
-
-static void connection_release(struct urcu_ref *ref)
-{
- struct relay_connection *conn =
- caa_container_of(ref, struct relay_connection, ref);
-
- if (conn->in_socket_ht) {
- struct lttng_ht_iter iter;
- int ret;
-
- iter.iter.node = &conn->sock_n.node;
- ret = lttng_ht_del(conn->socket_ht, &iter);
- LTTNG_ASSERT(!ret);
- }
-
- if (conn->session) {
- if (session_close(conn->session)) {
- ERR("session_close");
- }
- conn->session = NULL;
- }
- if (conn->viewer_session) {
- viewer_session_close(conn->viewer_session);
- }
- destroy_connection(conn);
-}
-
-void connection_put(struct relay_connection *conn)
-{
- rcu_read_lock();
- urcu_ref_put(&conn->ref, connection_release);
- rcu_read_unlock();
-}
-
-void connection_ht_add(struct lttng_ht *relay_connections_ht,
- struct relay_connection *conn)
-{
- LTTNG_ASSERT(!conn->in_socket_ht);
- lttng_ht_add_unique_ulong(relay_connections_ht, &conn->sock_n);
- conn->in_socket_ht = 1;
- conn->socket_ht = relay_connections_ht;
-}
-
-int connection_set_session(struct relay_connection *conn,
- struct relay_session *session)
-{
- int ret = 0;
-
- LTTNG_ASSERT(conn);
- LTTNG_ASSERT(session);
- LTTNG_ASSERT(!conn->session);
-
- if (connection_get(conn)) {
- if (session_get(session)) {
- conn->session = session;
- } else {
- ERR("Failed to get session reference in connection_set_session()");
- ret = -1;
- }
- connection_put(conn);
- } else {
- ERR("Failed to get connection reference in connection_set_session()");
- ret = -1;
- }
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <common/common.h>
+#include <urcu/rculist.h>
+
+#include "connection.h"
+#include "stream.h"
+#include "viewer-session.h"
+
+bool connection_get(struct relay_connection *conn)
+{
+ return urcu_ref_get_unless_zero(&conn->ref);
+}
+
+struct relay_connection *connection_get_by_sock(struct lttng_ht *relay_connections_ht,
+ int sock)
+{
+ struct lttng_ht_node_ulong *node;
+ struct lttng_ht_iter iter;
+ struct relay_connection *conn = NULL;
+
+ LTTNG_ASSERT(sock >= 0);
+
+ rcu_read_lock();
+ lttng_ht_lookup(relay_connections_ht, (void *)((unsigned long) sock),
+ &iter);
+ node = lttng_ht_iter_get_node_ulong(&iter);
+ if (!node) {
+ DBG2("Relay connection by sock %d not found", sock);
+ goto end;
+ }
+ conn = caa_container_of(node, struct relay_connection, sock_n);
+ if (!connection_get(conn)) {
+ conn = NULL;
+ }
+end:
+ rcu_read_unlock();
+ return conn;
+}
+
+int connection_reset_protocol_state(struct relay_connection *connection)
+{
+ int ret = 0;
+
+ switch (connection->type) {
+ case RELAY_DATA:
+ connection->protocol.data.state_id =
+ DATA_CONNECTION_STATE_RECEIVE_HEADER;
+ memset(&connection->protocol.data.state.receive_header,
+ 0,
+ sizeof(connection->protocol.data.state.receive_header));
+ connection->protocol.data.state.receive_header.left_to_receive =
+ sizeof(struct lttcomm_relayd_data_hdr);
+ break;
+ case RELAY_CONTROL:
+ connection->protocol.ctrl.state_id =
+ CTRL_CONNECTION_STATE_RECEIVE_HEADER;
+ memset(&connection->protocol.ctrl.state.receive_header,
+ 0,
+ sizeof(connection->protocol.ctrl.state.receive_header));
+ connection->protocol.data.state.receive_header.left_to_receive =
+ sizeof(struct lttcomm_relayd_hdr);
+ ret = lttng_dynamic_buffer_set_size(
+ &connection->protocol.ctrl.reception_buffer,
+ sizeof(struct lttcomm_relayd_hdr));
+ if (ret) {
+ ERR("Failed to reinitialize control connection reception buffer size to %zu bytes.", sizeof(struct lttcomm_relayd_hdr));
+ goto end;
+ }
+ break;
+ default:
+ goto end;
+ }
+ DBG("Reset communication state of relay connection (fd = %i)",
+ connection->sock->fd);
+end:
+ return ret;
+}
+
+struct relay_connection *connection_create(struct lttcomm_sock *sock,
+ enum connection_type type)
+{
+ struct relay_connection *conn;
+
+ conn = (relay_connection *) zmalloc(sizeof(*conn));
+ if (!conn) {
+ PERROR("zmalloc relay connection");
+ goto end;
+ }
+ urcu_ref_init(&conn->ref);
+ conn->type = type;
+ conn->sock = sock;
+ lttng_ht_node_init_ulong(&conn->sock_n, (unsigned long) conn->sock->fd);
+ if (conn->type == RELAY_CONTROL) {
+ lttng_dynamic_buffer_init(&conn->protocol.ctrl.reception_buffer);
+ }
+ connection_reset_protocol_state(conn);
+end:
+ return conn;
+}
+
+static void rcu_free_connection(struct rcu_head *head)
+{
+ struct relay_connection *conn =
+ caa_container_of(head, struct relay_connection, rcu_node);
+
+ lttcomm_destroy_sock(conn->sock);
+ if (conn->viewer_session) {
+ viewer_session_destroy(conn->viewer_session);
+ conn->viewer_session = NULL;
+ }
+ if (conn->type == RELAY_CONTROL) {
+ lttng_dynamic_buffer_reset(
+ &conn->protocol.ctrl.reception_buffer);
+ }
+ free(conn);
+}
+
+static void destroy_connection(struct relay_connection *conn)
+{
+ call_rcu(&conn->rcu_node, rcu_free_connection);
+}
+
+static void connection_release(struct urcu_ref *ref)
+{
+ struct relay_connection *conn =
+ caa_container_of(ref, struct relay_connection, ref);
+
+ if (conn->in_socket_ht) {
+ struct lttng_ht_iter iter;
+ int ret;
+
+ iter.iter.node = &conn->sock_n.node;
+ ret = lttng_ht_del(conn->socket_ht, &iter);
+ LTTNG_ASSERT(!ret);
+ }
+
+ if (conn->session) {
+ if (session_close(conn->session)) {
+ ERR("session_close");
+ }
+ conn->session = NULL;
+ }
+ if (conn->viewer_session) {
+ viewer_session_close(conn->viewer_session);
+ }
+ destroy_connection(conn);
+}
+
+void connection_put(struct relay_connection *conn)
+{
+ rcu_read_lock();
+ urcu_ref_put(&conn->ref, connection_release);
+ rcu_read_unlock();
+}
+
+void connection_ht_add(struct lttng_ht *relay_connections_ht,
+ struct relay_connection *conn)
+{
+ LTTNG_ASSERT(!conn->in_socket_ht);
+ lttng_ht_add_unique_ulong(relay_connections_ht, &conn->sock_n);
+ conn->in_socket_ht = 1;
+ conn->socket_ht = relay_connections_ht;
+}
+
+int connection_set_session(struct relay_connection *conn,
+ struct relay_session *session)
+{
+ int ret = 0;
+
+ LTTNG_ASSERT(conn);
+ LTTNG_ASSERT(session);
+ LTTNG_ASSERT(!conn->session);
+
+ if (connection_get(conn)) {
+ if (session_get(session)) {
+ conn->session = session;
+ } else {
+ ERR("Failed to get session reference in connection_set_session()");
+ ret = -1;
+ }
+ connection_put(conn);
+ } else {
+ ERR("Failed to get connection reference in connection_set_session()");
+ ret = -1;
+ }
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-
-#include <common/common.h>
-#include <common/utils.h>
-#include <urcu/rculist.h>
-
-#include "ctf-trace.h"
-#include "lttng-relayd.h"
-#include "stream.h"
-
-static uint64_t last_relay_ctf_trace_id;
-static pthread_mutex_t last_relay_ctf_trace_id_lock = PTHREAD_MUTEX_INITIALIZER;
-
-static void rcu_destroy_ctf_trace(struct rcu_head *rcu_head)
-{
- struct ctf_trace *trace =
- caa_container_of(rcu_head, struct ctf_trace, rcu_node);
-
- free(trace);
-}
-
-/*
- * Destroy a ctf trace and all stream contained in it.
- *
- * MUST be called with the RCU read side lock.
- */
-static void ctf_trace_destroy(struct ctf_trace *trace)
-{
- /*
- * Getting to this point, every stream referenced by that trace
- * have put back their ref since the've been closed by the
- * control side.
- */
- LTTNG_ASSERT(cds_list_empty(&trace->stream_list));
- session_put(trace->session);
- trace->session = NULL;
- free(trace->path);
- trace->path = NULL;
- call_rcu(&trace->rcu_node, rcu_destroy_ctf_trace);
-}
-
-static void ctf_trace_release(struct urcu_ref *ref)
-{
- struct ctf_trace *trace =
- caa_container_of(ref, struct ctf_trace, ref);
- int ret;
- struct lttng_ht_iter iter;
-
- iter.iter.node = &trace->node.node;
- ret = lttng_ht_del(trace->session->ctf_traces_ht, &iter);
- LTTNG_ASSERT(!ret);
- ctf_trace_destroy(trace);
-}
-
-/*
- * Should be called with RCU read-side lock held.
- */
-bool ctf_trace_get(struct ctf_trace *trace)
-{
- return urcu_ref_get_unless_zero(&trace->ref);
-}
-
-/*
- * Create and return an allocated ctf_trace. NULL on error.
- * There is no "open" and "close" for a ctf_trace, but rather just a
- * create and refcounting. Whenever all the streams belonging to a trace
- * put their reference, its refcount drops to 0.
- */
-static struct ctf_trace *ctf_trace_create(struct relay_session *session,
- const char *subpath)
-{
- struct ctf_trace *trace;
-
- trace = zmalloc(sizeof(*trace));
- if (!trace) {
- PERROR("Failed to allocate ctf_trace");
- goto end;
- }
- urcu_ref_init(&trace->ref);
-
- if (!session_get(session)) {
- ERR("Failed to acquire session reference");
- goto error;
- }
- trace->session = session;
- trace->path = strdup(subpath);
- if (!trace->path) {
- goto error;
- }
-
- CDS_INIT_LIST_HEAD(&trace->stream_list);
-
- pthread_mutex_lock(&last_relay_ctf_trace_id_lock);
- trace->id = ++last_relay_ctf_trace_id;
- pthread_mutex_unlock(&last_relay_ctf_trace_id_lock);
-
- lttng_ht_node_init_str(&trace->node, trace->path);
- trace->session = session;
- pthread_mutex_init(&trace->lock, NULL);
- pthread_mutex_init(&trace->stream_list_lock, NULL);
- lttng_ht_add_str(session->ctf_traces_ht, &trace->node);
-
- DBG("Created ctf_trace %" PRIu64 "of session \"%s\" from host \"%s\" with path: %s",
- trace->id, session->session_name, session->hostname,
- subpath);
-
-end:
- return trace;
-error:
- ctf_trace_put(trace);
- return NULL;
-}
-
-/*
- * Return a ctf_trace if found by id in the given hash table else NULL.
- * Hold a reference on the ctf_trace, and must be paired with
- * ctf_trace_put().
- */
-struct ctf_trace *ctf_trace_get_by_path_or_create(struct relay_session *session,
- const char *subpath)
-{
- struct lttng_ht_node_str *node;
- struct lttng_ht_iter iter;
- struct ctf_trace *trace = NULL;
-
- rcu_read_lock();
- lttng_ht_lookup(session->ctf_traces_ht, subpath, &iter);
- node = lttng_ht_iter_get_node_str(&iter);
- if (!node) {
- DBG("CTF Trace path %s not found", subpath);
- goto end;
- }
- trace = caa_container_of(node, struct ctf_trace, node);
- if (!ctf_trace_get(trace)) {
- trace = NULL;
- }
-end:
- rcu_read_unlock();
- if (!trace) {
- /* Try to create */
- trace = ctf_trace_create(session, subpath);
- }
- return trace;
-}
-
-void ctf_trace_put(struct ctf_trace *trace)
-{
- rcu_read_lock();
- urcu_ref_put(&trace->ref, ctf_trace_release);
- rcu_read_unlock();
-}
-
-int ctf_trace_close(struct ctf_trace *trace)
-{
- struct relay_stream *stream;
-
- rcu_read_lock();
- cds_list_for_each_entry_rcu(stream, &trace->stream_list,
- stream_node) {
- /*
- * Close stream since the connection owning the trace is being
- * torn down.
- */
- try_stream_close(stream);
- }
- rcu_read_unlock();
- /*
- * Since all references to the trace are held by its streams, we
- * don't need to do any self-ref put.
- */
- return 0;
-}
-
-struct relay_viewer_stream *ctf_trace_get_viewer_metadata_stream(struct ctf_trace *trace)
-{
- struct relay_viewer_stream *vstream;
-
- rcu_read_lock();
- vstream = rcu_dereference(trace->viewer_metadata_stream);
- if (!vstream) {
- goto end;
- }
- if (!viewer_stream_get(vstream)) {
- vstream = NULL;
- }
-end:
- rcu_read_unlock();
- return vstream;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+
+#include <common/common.h>
+#include <common/utils.h>
+#include <urcu/rculist.h>
+
+#include "ctf-trace.h"
+#include "lttng-relayd.h"
+#include "stream.h"
+
+static uint64_t last_relay_ctf_trace_id;
+static pthread_mutex_t last_relay_ctf_trace_id_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void rcu_destroy_ctf_trace(struct rcu_head *rcu_head)
+{
+ struct ctf_trace *trace =
+ caa_container_of(rcu_head, struct ctf_trace, rcu_node);
+
+ free(trace);
+}
+
+/*
+ * Destroy a ctf trace and all stream contained in it.
+ *
+ * MUST be called with the RCU read side lock.
+ */
+static void ctf_trace_destroy(struct ctf_trace *trace)
+{
+ /*
+ * Getting to this point, every stream referenced by that trace
+ * have put back their ref since the've been closed by the
+ * control side.
+ */
+ LTTNG_ASSERT(cds_list_empty(&trace->stream_list));
+ session_put(trace->session);
+ trace->session = NULL;
+ free(trace->path);
+ trace->path = NULL;
+ call_rcu(&trace->rcu_node, rcu_destroy_ctf_trace);
+}
+
+static void ctf_trace_release(struct urcu_ref *ref)
+{
+ struct ctf_trace *trace =
+ caa_container_of(ref, struct ctf_trace, ref);
+ int ret;
+ struct lttng_ht_iter iter;
+
+ iter.iter.node = &trace->node.node;
+ ret = lttng_ht_del(trace->session->ctf_traces_ht, &iter);
+ LTTNG_ASSERT(!ret);
+ ctf_trace_destroy(trace);
+}
+
+/*
+ * Should be called with RCU read-side lock held.
+ */
+bool ctf_trace_get(struct ctf_trace *trace)
+{
+ return urcu_ref_get_unless_zero(&trace->ref);
+}
+
+/*
+ * Create and return an allocated ctf_trace. NULL on error.
+ * There is no "open" and "close" for a ctf_trace, but rather just a
+ * create and refcounting. Whenever all the streams belonging to a trace
+ * put their reference, its refcount drops to 0.
+ */
+static struct ctf_trace *ctf_trace_create(struct relay_session *session,
+ const char *subpath)
+{
+ struct ctf_trace *trace;
+
+ trace = (ctf_trace *) zmalloc(sizeof(*trace));
+ if (!trace) {
+ PERROR("Failed to allocate ctf_trace");
+ goto end;
+ }
+ urcu_ref_init(&trace->ref);
+
+ if (!session_get(session)) {
+ ERR("Failed to acquire session reference");
+ goto error;
+ }
+ trace->session = session;
+ trace->path = strdup(subpath);
+ if (!trace->path) {
+ goto error;
+ }
+
+ CDS_INIT_LIST_HEAD(&trace->stream_list);
+
+ pthread_mutex_lock(&last_relay_ctf_trace_id_lock);
+ trace->id = ++last_relay_ctf_trace_id;
+ pthread_mutex_unlock(&last_relay_ctf_trace_id_lock);
+
+ lttng_ht_node_init_str(&trace->node, trace->path);
+ trace->session = session;
+ pthread_mutex_init(&trace->lock, NULL);
+ pthread_mutex_init(&trace->stream_list_lock, NULL);
+ lttng_ht_add_str(session->ctf_traces_ht, &trace->node);
+
+ DBG("Created ctf_trace %" PRIu64 "of session \"%s\" from host \"%s\" with path: %s",
+ trace->id, session->session_name, session->hostname,
+ subpath);
+
+end:
+ return trace;
+error:
+ ctf_trace_put(trace);
+ return NULL;
+}
+
+/*
+ * Return a ctf_trace if found by id in the given hash table else NULL.
+ * Hold a reference on the ctf_trace, and must be paired with
+ * ctf_trace_put().
+ */
+struct ctf_trace *ctf_trace_get_by_path_or_create(struct relay_session *session,
+ const char *subpath)
+{
+ struct lttng_ht_node_str *node;
+ struct lttng_ht_iter iter;
+ struct ctf_trace *trace = NULL;
+
+ rcu_read_lock();
+ lttng_ht_lookup(session->ctf_traces_ht, subpath, &iter);
+ node = lttng_ht_iter_get_node_str(&iter);
+ if (!node) {
+ DBG("CTF Trace path %s not found", subpath);
+ goto end;
+ }
+ trace = caa_container_of(node, struct ctf_trace, node);
+ if (!ctf_trace_get(trace)) {
+ trace = NULL;
+ }
+end:
+ rcu_read_unlock();
+ if (!trace) {
+ /* Try to create */
+ trace = ctf_trace_create(session, subpath);
+ }
+ return trace;
+}
+
+void ctf_trace_put(struct ctf_trace *trace)
+{
+ rcu_read_lock();
+ urcu_ref_put(&trace->ref, ctf_trace_release);
+ rcu_read_unlock();
+}
+
+int ctf_trace_close(struct ctf_trace *trace)
+{
+ struct relay_stream *stream;
+
+ rcu_read_lock();
+ cds_list_for_each_entry_rcu(stream, &trace->stream_list,
+ stream_node) {
+ /*
+ * Close stream since the connection owning the trace is being
+ * torn down.
+ */
+ try_stream_close(stream);
+ }
+ rcu_read_unlock();
+ /*
+ * Since all references to the trace are held by its streams, we
+ * don't need to do any self-ref put.
+ */
+ return 0;
+}
+
+struct relay_viewer_stream *ctf_trace_get_viewer_metadata_stream(struct ctf_trace *trace)
+{
+ struct relay_viewer_stream *vstream;
+
+ rcu_read_lock();
+ vstream = rcu_dereference(trace->viewer_metadata_stream);
+ if (!vstream) {
+ goto end;
+ }
+ if (!viewer_stream_get(vstream)) {
+ vstream = NULL;
+ }
+end:
+ rcu_read_unlock();
+ return vstream;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <fcntl.h>
-#include <getopt.h>
-#include <grp.h>
-#include <limits.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ipc.h>
-#include <sys/resource.h>
-#include <sys/shm.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <urcu/list.h>
-#include <poll.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <urcu/compiler.h>
-#include <inttypes.h>
-
-#include <common/defaults.h>
-#include <common/common.h>
-#include <common/consumer/consumer.h>
-#include <common/consumer/consumer-timer.h>
-#include <common/compat/poll.h>
-#include <common/sessiond-comm/sessiond-comm.h>
-#include <common/utils.h>
-#include <common/compat/getenv.h>
-#include <common/fd-tracker/utils.h>
-
-#include "lttng-relayd.h"
-#include "health-relayd.h"
-
-/* Global health check unix path */
-static
-char health_unix_sock_path[PATH_MAX];
-
-int health_quit_pipe[2] = { -1, -1 };
-
-/*
- * Check if the thread quit pipe was triggered.
- *
- * Return 1 if it was triggered else 0;
- */
-static
-int check_health_quit_pipe(int fd, uint32_t events)
-{
- if (fd == health_quit_pipe[0] && (events & LPOLLIN)) {
- return 1;
- }
-
- return 0;
-}
-
-/*
- * Send data on a unix socket using the liblttsessiondcomm API.
- *
- * Return lttcomm error code.
- */
-static int send_unix_sock(int sock, void *buf, size_t len)
-{
- /* Check valid length */
- if (len == 0) {
- return -1;
- }
-
- return lttcomm_send_unix_sock(sock, buf, len);
-}
-
-static int create_lttng_rundir_with_perm(const char *rundir)
-{
- int ret;
-
- DBG3("Creating LTTng run directory: %s", rundir);
-
- ret = mkdir(rundir, S_IRWXU);
- if (ret < 0) {
- if (errno != EEXIST) {
- ERR("Unable to create %s", rundir);
- goto error;
- } else {
- ret = 0;
- }
- } else if (ret == 0) {
- int is_root = !getuid();
-
- if (is_root) {
- gid_t gid;
-
- ret = utils_get_group_id(tracing_group_name, true, &gid);
- if (ret) {
- /* Default to root group. */
- gid = 0;
- }
-
- ret = chown(rundir, 0, gid);
- if (ret < 0) {
- ERR("Unable to set group on %s", rundir);
- PERROR("chown");
- ret = -1;
- goto error;
- }
-
- ret = chmod(rundir,
- S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
- if (ret < 0) {
- ERR("Unable to set permissions on %s", health_unix_sock_path);
- PERROR("chmod");
- ret = -1;
- goto error;
- }
- }
- }
-
-error:
- return ret;
-}
-
-static
-int parse_health_env(void)
-{
- const char *health_path;
-
- health_path = lttng_secure_getenv(LTTNG_RELAYD_HEALTH_ENV);
- if (health_path) {
- strncpy(health_unix_sock_path, health_path,
- PATH_MAX);
- health_unix_sock_path[PATH_MAX - 1] = '\0';
- }
-
- return 0;
-}
-
-static
-int setup_health_path(void)
-{
- int is_root, ret = 0;
- const char *home_path = NULL;
- char *rundir = NULL, *relayd_path = NULL;
-
- ret = parse_health_env();
- if (ret) {
- return ret;
- }
-
- is_root = !getuid();
-
- if (is_root) {
- rundir = strdup(DEFAULT_LTTNG_RUNDIR);
- if (!rundir) {
- ret = -ENOMEM;
- goto end;
- }
- } else {
- /*
- * Create rundir from home path. This will create something like
- * $HOME/.lttng
- */
- home_path = utils_get_home_dir();
-
- if (home_path == NULL) {
- /* TODO: Add --socket PATH option */
- ERR("Can't get HOME directory for sockets creation.");
- ret = -EPERM;
- goto end;
- }
-
- ret = asprintf(&rundir, DEFAULT_LTTNG_HOME_RUNDIR, home_path);
- if (ret < 0) {
- ret = -ENOMEM;
- goto end;
- }
- }
-
- ret = asprintf(&relayd_path, DEFAULT_RELAYD_PATH, rundir);
- if (ret < 0) {
- ret = -ENOMEM;
- goto end;
- }
-
- ret = create_lttng_rundir_with_perm(rundir);
- if (ret < 0) {
- goto end;
- }
-
- ret = create_lttng_rundir_with_perm(relayd_path);
- if (ret < 0) {
- goto end;
- }
-
- if (is_root) {
- if (strlen(health_unix_sock_path) != 0) {
- goto end;
- }
- snprintf(health_unix_sock_path, sizeof(health_unix_sock_path),
- DEFAULT_GLOBAL_RELAY_HEALTH_UNIX_SOCK,
- (int) getpid());
- } else {
- /* Set health check Unix path */
- if (strlen(health_unix_sock_path) != 0) {
- goto end;
- }
-
- snprintf(health_unix_sock_path, sizeof(health_unix_sock_path),
- DEFAULT_HOME_RELAY_HEALTH_UNIX_SOCK,
- home_path, (int) getpid());
- }
-
-end:
- free(rundir);
- free(relayd_path);
- return ret;
-}
-
-static
-int accept_unix_socket(void *data, int *out_fd)
-{
- int ret;
- int accepting_sock = *((int *) data);
-
- ret = lttcomm_accept_unix_sock(accepting_sock);
- if (ret < 0) {
- goto end;
- }
-
- *out_fd = ret;
- ret = 0;
-end:
- return ret;
-}
-
-static
-int open_unix_socket(void *data, int *out_fd)
-{
- int ret;
- const char *path = data;
-
- ret = lttcomm_create_unix_sock(path);
- if (ret < 0) {
- goto end;
- }
-
- *out_fd = ret;
- ret = 0;
-end:
- return ret;
-}
-
-/*
- * Thread managing health check socket.
- */
-void *thread_manage_health(void *data)
-{
- int sock = -1, new_sock = -1, ret, i, pollfd, err = -1;
- uint32_t revents, nb_fd;
- struct lttng_poll_event events;
- struct health_comm_msg msg;
- struct health_comm_reply reply;
- int is_root;
- char *sock_name;
-
- DBG("[thread] Manage health check started");
-
- setup_health_path();
-
- rcu_register_thread();
-
- /* We might hit an error path before this is created. */
- lttng_poll_init(&events);
-
- /* Create unix socket */
- ret = asprintf(&sock_name, "Unix socket @ %s", health_unix_sock_path);
- if (ret == -1) {
- PERROR("Failed to allocate unix socket name");
- err = -1;
- goto error;
- }
- ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock,
- (const char **) &sock_name, 1, open_unix_socket,
- health_unix_sock_path);
- free(sock_name);
- if (ret < 0) {
- ERR("Unable to create health check Unix socket");
- err = -1;
- goto error;
- }
-
- is_root = !getuid();
- if (is_root) {
- /* lttng health client socket path permissions */
- gid_t gid;
-
- ret = utils_get_group_id(tracing_group_name, true, &gid);
- if (ret) {
- /* Default to root group. */
- gid = 0;
- }
-
- ret = chown(health_unix_sock_path, 0, gid);
- if (ret < 0) {
- ERR("Unable to set group on %s", health_unix_sock_path);
- PERROR("chown");
- err = -1;
- goto error;
- }
-
- ret = chmod(health_unix_sock_path,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
- if (ret < 0) {
- ERR("Unable to set permissions on %s", health_unix_sock_path);
- PERROR("chmod");
- err = -1;
- goto error;
- }
- }
-
- /*
- * Set the CLOEXEC flag. Return code is useless because either way, the
- * show must go on.
- */
- (void) utils_set_fd_cloexec(sock);
-
- ret = lttcomm_listen_unix_sock(sock);
- if (ret < 0) {
- goto error;
- }
-
- /* Size is set to 2 for the unix socket and quit pipe. */
- ret = fd_tracker_util_poll_create(the_fd_tracker,
- "Health management thread epoll", &events, 2,
- LTTNG_CLOEXEC);
- if (ret < 0) {
- ERR("Poll set creation failed");
- goto error;
- }
-
- ret = lttng_poll_add(&events, health_quit_pipe[0], LPOLLIN);
- if (ret < 0) {
- goto error;
- }
-
- /* Add the application registration socket */
- ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLPRI);
- if (ret < 0) {
- goto error;
- }
-
- lttng_relay_notify_ready();
-
- while (1) {
- char *accepted_socket_name;
-
- DBG("Health check ready");
-
- /* Inifinite blocking call, waiting for transmission */
-restart:
- ret = lttng_poll_wait(&events, -1);
- if (ret < 0) {
- /*
- * Restart interrupted system call.
- */
- if (errno == EINTR) {
- goto restart;
- }
- goto error;
- }
-
- nb_fd = ret;
-
- for (i = 0; i < nb_fd; i++) {
- /* Fetch once the poll data */
- revents = LTTNG_POLL_GETEV(&events, i);
- pollfd = LTTNG_POLL_GETFD(&events, i);
-
- /* Thread quit pipe has been closed. Killing thread. */
- ret = check_health_quit_pipe(pollfd, revents);
- if (ret) {
- err = 0;
- goto exit;
- }
-
- /* Event on the registration socket */
- if (pollfd == sock) {
- if (revents & LPOLLIN) {
- continue;
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- ERR("Health socket poll error");
- goto error;
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- goto error;
- }
- }
- }
-
- ret = asprintf(&accepted_socket_name, "Socket accepted from unix socket @ %s",
- health_unix_sock_path);
- if (ret == -1) {
- PERROR("Failed to allocate name of accepted socket from unix socket @ %s",
- health_unix_sock_path);
- goto error;
- }
- ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &new_sock,
- (const char **) &accepted_socket_name, 1,
- accept_unix_socket, &sock);
- free(accepted_socket_name);
- if (ret < 0) {
- goto error;
- }
-
- /*
- * Set the CLOEXEC flag. Return code is useless because either way, the
- * show must go on.
- */
- (void) utils_set_fd_cloexec(new_sock);
-
- DBG("Receiving data from client for health...");
- ret = lttcomm_recv_unix_sock(new_sock, (void *)&msg, sizeof(msg));
- if (ret <= 0) {
- DBG("Nothing recv() from client... continuing");
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- &new_sock, 1, fd_tracker_util_close_fd,
- NULL);
- if (ret) {
- PERROR("close");
- }
- new_sock = -1;
- continue;
- }
-
- rcu_thread_online();
-
- LTTNG_ASSERT(msg.cmd == HEALTH_CMD_CHECK);
-
- memset(&reply, 0, sizeof(reply));
- for (i = 0; i < NR_HEALTH_RELAYD_TYPES; i++) {
- /*
- * health_check_state return 0 if thread is in
- * error.
- */
- if (!health_check_state(health_relayd, i)) {
- reply.ret_code |= 1ULL << i;
- }
- }
-
- DBG2("Health check return value %" PRIx64, reply.ret_code);
-
- ret = send_unix_sock(new_sock, (void *) &reply, sizeof(reply));
- if (ret < 0) {
- ERR("Failed to send health data back to client");
- }
-
- /* End of transmission */
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- &new_sock, 1, fd_tracker_util_close_fd,
- NULL);
- if (ret) {
- PERROR("close");
- }
- new_sock = -1;
- }
-
-error:
- lttng_relay_stop_threads();
-exit:
- if (err) {
- ERR("Health error occurred in %s", __func__);
- }
- DBG("Health check thread dying");
- unlink(health_unix_sock_path);
- if (sock >= 0) {
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &sock,
- 1, fd_tracker_util_close_fd, NULL);
- if (ret) {
- PERROR("close");
- }
- }
-
- /*
- * We do NOT rmdir rundir nor the relayd path because there are
- * other processes using them.
- */
-
- (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
-
- rcu_unregister_thread();
- return NULL;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ipc.h>
+#include <sys/resource.h>
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <urcu/list.h>
+#include <poll.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <urcu/compiler.h>
+#include <inttypes.h>
+
+#include <common/defaults.h>
+#include <common/common.h>
+#include <common/consumer/consumer.h>
+#include <common/consumer/consumer-timer.h>
+#include <common/compat/poll.h>
+#include <common/sessiond-comm/sessiond-comm.h>
+#include <common/utils.h>
+#include <common/compat/getenv.h>
+#include <common/fd-tracker/utils.h>
+
+#include "lttng-relayd.h"
+#include "health-relayd.h"
+
+/* Global health check unix path */
+static
+char health_unix_sock_path[PATH_MAX];
+
+int health_quit_pipe[2] = { -1, -1 };
+
+/*
+ * Check if the thread quit pipe was triggered.
+ *
+ * Return 1 if it was triggered else 0;
+ */
+static
+int check_health_quit_pipe(int fd, uint32_t events)
+{
+ if (fd == health_quit_pipe[0] && (events & LPOLLIN)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Send data on a unix socket using the liblttsessiondcomm API.
+ *
+ * Return lttcomm error code.
+ */
+static int send_unix_sock(int sock, void *buf, size_t len)
+{
+ /* Check valid length */
+ if (len == 0) {
+ return -1;
+ }
+
+ return lttcomm_send_unix_sock(sock, buf, len);
+}
+
+static int create_lttng_rundir_with_perm(const char *rundir)
+{
+ int ret;
+
+ DBG3("Creating LTTng run directory: %s", rundir);
+
+ ret = mkdir(rundir, S_IRWXU);
+ if (ret < 0) {
+ if (errno != EEXIST) {
+ ERR("Unable to create %s", rundir);
+ goto error;
+ } else {
+ ret = 0;
+ }
+ } else if (ret == 0) {
+ int is_root = !getuid();
+
+ if (is_root) {
+ gid_t gid;
+
+ ret = utils_get_group_id(tracing_group_name, true, &gid);
+ if (ret) {
+ /* Default to root group. */
+ gid = 0;
+ }
+
+ ret = chown(rundir, 0, gid);
+ if (ret < 0) {
+ ERR("Unable to set group on %s", rundir);
+ PERROR("chown");
+ ret = -1;
+ goto error;
+ }
+
+ ret = chmod(rundir,
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+ if (ret < 0) {
+ ERR("Unable to set permissions on %s", health_unix_sock_path);
+ PERROR("chmod");
+ ret = -1;
+ goto error;
+ }
+ }
+ }
+
+error:
+ return ret;
+}
+
+static
+int parse_health_env(void)
+{
+ const char *health_path;
+
+ health_path = lttng_secure_getenv(LTTNG_RELAYD_HEALTH_ENV);
+ if (health_path) {
+ strncpy(health_unix_sock_path, health_path,
+ PATH_MAX);
+ health_unix_sock_path[PATH_MAX - 1] = '\0';
+ }
+
+ return 0;
+}
+
+static
+int setup_health_path(void)
+{
+ int is_root, ret = 0;
+ const char *home_path = NULL;
+ char *rundir = NULL, *relayd_path = NULL;
+
+ ret = parse_health_env();
+ if (ret) {
+ return ret;
+ }
+
+ is_root = !getuid();
+
+ if (is_root) {
+ rundir = strdup(DEFAULT_LTTNG_RUNDIR);
+ if (!rundir) {
+ ret = -ENOMEM;
+ goto end;
+ }
+ } else {
+ /*
+ * Create rundir from home path. This will create something like
+ * $HOME/.lttng
+ */
+ home_path = utils_get_home_dir();
+
+ if (home_path == NULL) {
+ /* TODO: Add --socket PATH option */
+ ERR("Can't get HOME directory for sockets creation.");
+ ret = -EPERM;
+ goto end;
+ }
+
+ ret = asprintf(&rundir, DEFAULT_LTTNG_HOME_RUNDIR, home_path);
+ if (ret < 0) {
+ ret = -ENOMEM;
+ goto end;
+ }
+ }
+
+ ret = asprintf(&relayd_path, DEFAULT_RELAYD_PATH, rundir);
+ if (ret < 0) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ ret = create_lttng_rundir_with_perm(rundir);
+ if (ret < 0) {
+ goto end;
+ }
+
+ ret = create_lttng_rundir_with_perm(relayd_path);
+ if (ret < 0) {
+ goto end;
+ }
+
+ if (is_root) {
+ if (strlen(health_unix_sock_path) != 0) {
+ goto end;
+ }
+ snprintf(health_unix_sock_path, sizeof(health_unix_sock_path),
+ DEFAULT_GLOBAL_RELAY_HEALTH_UNIX_SOCK,
+ (int) getpid());
+ } else {
+ /* Set health check Unix path */
+ if (strlen(health_unix_sock_path) != 0) {
+ goto end;
+ }
+
+ snprintf(health_unix_sock_path, sizeof(health_unix_sock_path),
+ DEFAULT_HOME_RELAY_HEALTH_UNIX_SOCK,
+ home_path, (int) getpid());
+ }
+
+end:
+ free(rundir);
+ free(relayd_path);
+ return ret;
+}
+
+static
+int accept_unix_socket(void *data, int *out_fd)
+{
+ int ret;
+ int accepting_sock = *((int *) data);
+
+ ret = lttcomm_accept_unix_sock(accepting_sock);
+ if (ret < 0) {
+ goto end;
+ }
+
+ *out_fd = ret;
+ ret = 0;
+end:
+ return ret;
+}
+
+static
+int open_unix_socket(void *data, int *out_fd)
+{
+ int ret;
+ const char *path = (const char *) data;
+
+ ret = lttcomm_create_unix_sock(path);
+ if (ret < 0) {
+ goto end;
+ }
+
+ *out_fd = ret;
+ ret = 0;
+end:
+ return ret;
+}
+
+/*
+ * Thread managing health check socket.
+ */
+void *thread_manage_health(void *data)
+{
+ int sock = -1, new_sock = -1, ret, i, pollfd, err = -1;
+ uint32_t revents, nb_fd;
+ struct lttng_poll_event events;
+ struct health_comm_msg msg;
+ struct health_comm_reply reply;
+ int is_root;
+ char *sock_name;
+
+ DBG("[thread] Manage health check started");
+
+ setup_health_path();
+
+ rcu_register_thread();
+
+ /* We might hit an error path before this is created. */
+ lttng_poll_init(&events);
+
+ /* Create unix socket */
+ ret = asprintf(&sock_name, "Unix socket @ %s", health_unix_sock_path);
+ if (ret == -1) {
+ PERROR("Failed to allocate unix socket name");
+ err = -1;
+ goto error;
+ }
+ ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock,
+ (const char **) &sock_name, 1, open_unix_socket,
+ health_unix_sock_path);
+ free(sock_name);
+ if (ret < 0) {
+ ERR("Unable to create health check Unix socket");
+ err = -1;
+ goto error;
+ }
+
+ is_root = !getuid();
+ if (is_root) {
+ /* lttng health client socket path permissions */
+ gid_t gid;
+
+ ret = utils_get_group_id(tracing_group_name, true, &gid);
+ if (ret) {
+ /* Default to root group. */
+ gid = 0;
+ }
+
+ ret = chown(health_unix_sock_path, 0, gid);
+ if (ret < 0) {
+ ERR("Unable to set group on %s", health_unix_sock_path);
+ PERROR("chown");
+ err = -1;
+ goto error;
+ }
+
+ ret = chmod(health_unix_sock_path,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ if (ret < 0) {
+ ERR("Unable to set permissions on %s", health_unix_sock_path);
+ PERROR("chmod");
+ err = -1;
+ goto error;
+ }
+ }
+
+ /*
+ * Set the CLOEXEC flag. Return code is useless because either way, the
+ * show must go on.
+ */
+ (void) utils_set_fd_cloexec(sock);
+
+ ret = lttcomm_listen_unix_sock(sock);
+ if (ret < 0) {
+ goto error;
+ }
+
+ /* Size is set to 2 for the unix socket and quit pipe. */
+ ret = fd_tracker_util_poll_create(the_fd_tracker,
+ "Health management thread epoll", &events, 2,
+ LTTNG_CLOEXEC);
+ if (ret < 0) {
+ ERR("Poll set creation failed");
+ goto error;
+ }
+
+ ret = lttng_poll_add(&events, health_quit_pipe[0], LPOLLIN);
+ if (ret < 0) {
+ goto error;
+ }
+
+ /* Add the application registration socket */
+ ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLPRI);
+ if (ret < 0) {
+ goto error;
+ }
+
+ lttng_relay_notify_ready();
+
+ while (1) {
+ char *accepted_socket_name;
+
+ DBG("Health check ready");
+
+ /* Inifinite blocking call, waiting for transmission */
+restart:
+ ret = lttng_poll_wait(&events, -1);
+ if (ret < 0) {
+ /*
+ * Restart interrupted system call.
+ */
+ if (errno == EINTR) {
+ goto restart;
+ }
+ goto error;
+ }
+
+ nb_fd = ret;
+
+ for (i = 0; i < nb_fd; i++) {
+ /* Fetch once the poll data */
+ revents = LTTNG_POLL_GETEV(&events, i);
+ pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ /* Thread quit pipe has been closed. Killing thread. */
+ ret = check_health_quit_pipe(pollfd, revents);
+ if (ret) {
+ err = 0;
+ goto exit;
+ }
+
+ /* Event on the registration socket */
+ if (pollfd == sock) {
+ if (revents & LPOLLIN) {
+ continue;
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ ERR("Health socket poll error");
+ goto error;
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ goto error;
+ }
+ }
+ }
+
+ ret = asprintf(&accepted_socket_name, "Socket accepted from unix socket @ %s",
+ health_unix_sock_path);
+ if (ret == -1) {
+ PERROR("Failed to allocate name of accepted socket from unix socket @ %s",
+ health_unix_sock_path);
+ goto error;
+ }
+ ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &new_sock,
+ (const char **) &accepted_socket_name, 1,
+ accept_unix_socket, &sock);
+ free(accepted_socket_name);
+ if (ret < 0) {
+ goto error;
+ }
+
+ /*
+ * Set the CLOEXEC flag. Return code is useless because either way, the
+ * show must go on.
+ */
+ (void) utils_set_fd_cloexec(new_sock);
+
+ DBG("Receiving data from client for health...");
+ ret = lttcomm_recv_unix_sock(new_sock, (void *)&msg, sizeof(msg));
+ if (ret <= 0) {
+ DBG("Nothing recv() from client... continuing");
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ &new_sock, 1, fd_tracker_util_close_fd,
+ NULL);
+ if (ret) {
+ PERROR("close");
+ }
+ new_sock = -1;
+ continue;
+ }
+
+ rcu_thread_online();
+
+ LTTNG_ASSERT(msg.cmd == HEALTH_CMD_CHECK);
+
+ memset(&reply, 0, sizeof(reply));
+ for (i = 0; i < NR_HEALTH_RELAYD_TYPES; i++) {
+ /*
+ * health_check_state return 0 if thread is in
+ * error.
+ */
+ if (!health_check_state(health_relayd, i)) {
+ reply.ret_code |= 1ULL << i;
+ }
+ }
+
+ DBG2("Health check return value %" PRIx64, reply.ret_code);
+
+ ret = send_unix_sock(new_sock, (void *) &reply, sizeof(reply));
+ if (ret < 0) {
+ ERR("Failed to send health data back to client");
+ }
+
+ /* End of transmission */
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ &new_sock, 1, fd_tracker_util_close_fd,
+ NULL);
+ if (ret) {
+ PERROR("close");
+ }
+ new_sock = -1;
+ }
+
+error:
+ lttng_relay_stop_threads();
+exit:
+ if (err) {
+ ERR("Health error occurred in %s", __func__);
+ }
+ DBG("Health check thread dying");
+ unlink(health_unix_sock_path);
+ if (sock >= 0) {
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &sock,
+ 1, fd_tracker_util_close_fd, NULL);
+ if (ret) {
+ PERROR("close");
+ }
+ }
+
+ /*
+ * We do NOT rmdir rundir nor the relayd path because there are
+ * other processes using them.
+ */
+
+ (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
+
+ rcu_unregister_thread();
+ return NULL;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-
-#include <common/common.h>
-#include <common/utils.h>
-#include <common/compat/endian.h>
-
-#include "lttng-relayd.h"
-#include "stream.h"
-#include "index.h"
-#include "connection.h"
-
-/*
- * Allocate a new relay index object. Pass the stream in which it is
- * contained as parameter. The sequence number will be used as the hash
- * table key.
- *
- * Called with stream mutex held.
- * Return allocated object or else NULL on error.
- */
-static struct relay_index *relay_index_create(struct relay_stream *stream,
- uint64_t net_seq_num)
-{
- struct relay_index *index;
-
- DBG2("Creating relay index for stream id %" PRIu64 " and seqnum %" PRIu64,
- stream->stream_handle, net_seq_num);
-
- index = zmalloc(sizeof(*index));
- if (!index) {
- PERROR("Relay index zmalloc");
- goto end;
- }
- if (!stream_get(stream)) {
- ERR("Cannot get stream");
- free(index);
- index = NULL;
- goto end;
- }
- index->stream = stream;
-
- lttng_ht_node_init_u64(&index->index_n, net_seq_num);
- pthread_mutex_init(&index->lock, NULL);
- urcu_ref_init(&index->ref);
-
-end:
- return index;
-}
-
-/*
- * Add unique relay index to the given hash table. In case of a collision, the
- * already existing object is put in the given _index variable.
- *
- * RCU read side lock MUST be acquired.
- */
-static struct relay_index *relay_index_add_unique(struct relay_stream *stream,
- struct relay_index *index)
-{
- struct cds_lfht_node *node_ptr;
- struct relay_index *_index;
-
- DBG2("Adding relay index with stream id %" PRIu64 " and seqnum %" PRIu64,
- stream->stream_handle, index->index_n.key);
-
- node_ptr = cds_lfht_add_unique(stream->indexes_ht->ht,
- stream->indexes_ht->hash_fct(&index->index_n, lttng_ht_seed),
- stream->indexes_ht->match_fct, &index->index_n,
- &index->index_n.node);
- if (node_ptr != &index->index_n.node) {
- _index = caa_container_of(node_ptr, struct relay_index,
- index_n.node);
- } else {
- _index = NULL;
- }
- return _index;
-}
-
-/*
- * Should be called with RCU read-side lock held.
- */
-static bool relay_index_get(struct relay_index *index)
-{
- DBG2("index get for stream id %" PRIu64 " and seqnum %" PRIu64 " refcount %d",
- index->stream->stream_handle, index->index_n.key,
- (int) index->ref.refcount);
-
- return urcu_ref_get_unless_zero(&index->ref);
-}
-
-/*
- * Get a relayd index in within the given stream, or create it if not
- * present.
- *
- * Called with stream mutex held.
- * Return index object or else NULL on error.
- */
-struct relay_index *relay_index_get_by_id_or_create(struct relay_stream *stream,
- uint64_t net_seq_num)
-{
- struct lttng_ht_node_u64 *node;
- struct lttng_ht_iter iter;
- struct relay_index *index = NULL;
-
- DBG3("Finding index for stream id %" PRIu64 " and seq_num %" PRIu64,
- stream->stream_handle, net_seq_num);
-
- rcu_read_lock();
- lttng_ht_lookup(stream->indexes_ht, &net_seq_num, &iter);
- node = lttng_ht_iter_get_node_u64(&iter);
- if (node) {
- index = caa_container_of(node, struct relay_index, index_n);
- } else {
- struct relay_index *oldindex;
-
- index = relay_index_create(stream, net_seq_num);
- if (!index) {
- ERR("Cannot create index for stream id %" PRIu64 " and seq_num %" PRIu64,
- stream->stream_handle, net_seq_num);
- goto end;
- }
- oldindex = relay_index_add_unique(stream, index);
- if (oldindex) {
- /* Added concurrently, keep old. */
- relay_index_put(index);
- index = oldindex;
- if (!relay_index_get(index)) {
- index = NULL;
- }
- } else {
- stream->indexes_in_flight++;
- index->in_hash_table = true;
- }
- }
-end:
- rcu_read_unlock();
- DBG2("Index %sfound or created in HT for stream ID %" PRIu64 " and seqnum %" PRIu64,
- (index == NULL) ? "NOT " : "", stream->stream_handle, net_seq_num);
- return index;
-}
-
-int relay_index_set_file(struct relay_index *index,
- struct lttng_index_file *index_file,
- uint64_t data_offset)
-{
- int ret = 0;
-
- pthread_mutex_lock(&index->lock);
- if (index->index_file) {
- ret = -1;
- goto end;
- }
- lttng_index_file_get(index_file);
- index->index_file = index_file;
- index->index_data.offset = data_offset;
-end:
- pthread_mutex_unlock(&index->lock);
- return ret;
-}
-
-int relay_index_set_data(struct relay_index *index,
- const struct ctf_packet_index *data)
-{
- int ret = 0;
-
- pthread_mutex_lock(&index->lock);
- if (index->has_index_data) {
- ret = -1;
- goto end;
- }
- /* Set everything except data_offset. */
- index->index_data.packet_size = data->packet_size;
- index->index_data.content_size = data->content_size;
- index->index_data.timestamp_begin = data->timestamp_begin;
- index->index_data.timestamp_end = data->timestamp_end;
- index->index_data.events_discarded = data->events_discarded;
- index->index_data.stream_id = data->stream_id;
- index->has_index_data = true;
-end:
- pthread_mutex_unlock(&index->lock);
- return ret;
-}
-
-static void index_destroy(struct relay_index *index)
-{
- free(index);
-}
-
-static void index_destroy_rcu(struct rcu_head *rcu_head)
-{
- struct relay_index *index =
- caa_container_of(rcu_head, struct relay_index, rcu_node);
-
- index_destroy(index);
-}
-
-/* Stream lock must be held by the caller. */
-static void index_release(struct urcu_ref *ref)
-{
- struct relay_index *index = caa_container_of(ref, struct relay_index, ref);
- struct relay_stream *stream = index->stream;
- int ret;
- struct lttng_ht_iter iter;
-
- if (index->index_file) {
- lttng_index_file_put(index->index_file);
- index->index_file = NULL;
- }
- if (index->in_hash_table) {
- /* Delete index from hash table. */
- iter.iter.node = &index->index_n.node;
- ret = lttng_ht_del(stream->indexes_ht, &iter);
- LTTNG_ASSERT(!ret);
- stream->indexes_in_flight--;
- }
-
- stream_put(index->stream);
- index->stream = NULL;
-
- call_rcu(&index->rcu_node, index_destroy_rcu);
-}
-
-/*
- * Called with stream mutex held.
- *
- * Stream lock must be held by the caller.
- */
-void relay_index_put(struct relay_index *index)
-{
- DBG2("index put for stream id %" PRIu64 " and seqnum %" PRIu64 " refcount %d",
- index->stream->stream_handle, index->index_n.key,
- (int) index->ref.refcount);
- /*
- * Ensure existance of index->lock for index unlock.
- */
- rcu_read_lock();
- /*
- * Index lock ensures that concurrent test and update of stream
- * ref is atomic.
- */
- LTTNG_ASSERT(index->ref.refcount != 0);
- urcu_ref_put(&index->ref, index_release);
- rcu_read_unlock();
-}
-
-/*
- * Try to flush index to disk. Releases self-reference to index once
- * flush succeeds.
- *
- * Stream lock must be held by the caller.
- * Return 0 on successful flush, a negative value on error, or positive
- * value if no flush was performed.
- */
-int relay_index_try_flush(struct relay_index *index)
-{
- int ret = 1;
- bool flushed = false;
-
- pthread_mutex_lock(&index->lock);
- if (index->flushed) {
- goto skip;
- }
- /* Check if we are ready to flush. */
- if (!index->has_index_data || !index->index_file) {
- goto skip;
- }
-
- DBG2("Writing index for stream ID %" PRIu64 " and seq num %" PRIu64,
- index->stream->stream_handle, index->index_n.key);
- flushed = true;
- index->flushed = true;
- ret = lttng_index_file_write(index->index_file, &index->index_data);
-skip:
- pthread_mutex_unlock(&index->lock);
-
- if (flushed) {
- /* Put self-ref from index now that it has been flushed. */
- relay_index_put(index);
- }
- return ret;
-}
-
-/*
- * Close every relay index within a given stream, without flushing
- * them.
- */
-void relay_index_close_all(struct relay_stream *stream)
-{
- struct lttng_ht_iter iter;
- struct relay_index *index;
-
- rcu_read_lock();
- cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
- index, index_n.node) {
- /* Put self-ref from index. */
- relay_index_put(index);
- }
- rcu_read_unlock();
-}
-
-void relay_index_close_partial_fd(struct relay_stream *stream)
-{
- struct lttng_ht_iter iter;
- struct relay_index *index;
-
- rcu_read_lock();
- cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
- index, index_n.node) {
- if (!index->index_file) {
- continue;
- }
- /*
- * Partial index has its index_file: we have only
- * received its info from the data socket.
- * Put self-ref from index.
- */
- relay_index_put(index);
- }
- rcu_read_unlock();
-}
-
-uint64_t relay_index_find_last(struct relay_stream *stream)
-{
- struct lttng_ht_iter iter;
- struct relay_index *index;
- uint64_t net_seq_num = -1ULL;
-
- rcu_read_lock();
- cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
- index, index_n.node) {
- if (net_seq_num == -1ULL ||
- index->index_n.key > net_seq_num) {
- net_seq_num = index->index_n.key;
- }
- }
- rcu_read_unlock();
- return net_seq_num;
-}
-
-/*
- * Update the index file of an already existing relay_index.
- * Offsets by 'removed_data_count' the offset field of an index.
- */
-static
-int relay_index_switch_file(struct relay_index *index,
- struct lttng_index_file *new_index_file,
- uint64_t removed_data_count)
-{
- int ret = 0;
- uint64_t offset;
-
- pthread_mutex_lock(&index->lock);
- if (!index->index_file) {
- ERR("No index_file");
- ret = 0;
- goto end;
- }
-
- lttng_index_file_put(index->index_file);
- lttng_index_file_get(new_index_file);
- index->index_file = new_index_file;
- offset = be64toh(index->index_data.offset);
- index->index_data.offset = htobe64(offset - removed_data_count);
-
-end:
- pthread_mutex_unlock(&index->lock);
- return ret;
-}
-
-/*
- * Switch the index file of all pending indexes for a stream and update the
- * data offset by substracting the last safe position.
- * Stream lock must be held.
- */
-int relay_index_switch_all_files(struct relay_stream *stream)
-{
- struct lttng_ht_iter iter;
- struct relay_index *index;
- int ret = 0;
-
- rcu_read_lock();
- cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
- index, index_n.node) {
- ret = relay_index_switch_file(index, stream->index_file,
- stream->pos_after_last_complete_data_index);
- if (ret) {
- goto end;
- }
- }
-end:
- rcu_read_unlock();
- return ret;
-}
-
-/*
- * Set index data from the control port to a given index object.
- */
-int relay_index_set_control_data(struct relay_index *index,
- const struct lttcomm_relayd_index *data,
- unsigned int minor_version)
-{
- /* The index on disk is encoded in big endian. */
- const struct ctf_packet_index index_data = {
- .packet_size = htobe64(data->packet_size),
- .content_size = htobe64(data->content_size),
- .timestamp_begin = htobe64(data->timestamp_begin),
- .timestamp_end = htobe64(data->timestamp_end),
- .events_discarded = htobe64(data->events_discarded),
- .stream_id = htobe64(data->stream_id),
- };
-
- if (minor_version >= 8) {
- index->index_data.stream_instance_id = htobe64(data->stream_instance_id);
- index->index_data.packet_seq_num = htobe64(data->packet_seq_num);
- } else {
- uint64_t unset_value = -1ULL;
-
- index->index_data.stream_instance_id = htobe64(unset_value);
- index->index_data.packet_seq_num = htobe64(unset_value);
- }
-
- return relay_index_set_data(index, &index_data);
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+
+#include <common/common.h>
+#include <common/utils.h>
+#include <common/compat/endian.h>
+
+#include "lttng-relayd.h"
+#include "stream.h"
+#include "index.h"
+#include "connection.h"
+
+/*
+ * Allocate a new relay index object. Pass the stream in which it is
+ * contained as parameter. The sequence number will be used as the hash
+ * table key.
+ *
+ * Called with stream mutex held.
+ * Return allocated object or else NULL on error.
+ */
+static struct relay_index *relay_index_create(struct relay_stream *stream,
+ uint64_t net_seq_num)
+{
+ struct relay_index *index;
+
+ DBG2("Creating relay index for stream id %" PRIu64 " and seqnum %" PRIu64,
+ stream->stream_handle, net_seq_num);
+
+ index = (relay_index *) zmalloc(sizeof(*index));
+ if (!index) {
+ PERROR("Relay index zmalloc");
+ goto end;
+ }
+ if (!stream_get(stream)) {
+ ERR("Cannot get stream");
+ free(index);
+ index = NULL;
+ goto end;
+ }
+ index->stream = stream;
+
+ lttng_ht_node_init_u64(&index->index_n, net_seq_num);
+ pthread_mutex_init(&index->lock, NULL);
+ urcu_ref_init(&index->ref);
+
+end:
+ return index;
+}
+
+/*
+ * Add unique relay index to the given hash table. In case of a collision, the
+ * already existing object is put in the given _index variable.
+ *
+ * RCU read side lock MUST be acquired.
+ */
+static struct relay_index *relay_index_add_unique(struct relay_stream *stream,
+ struct relay_index *index)
+{
+ struct cds_lfht_node *node_ptr;
+ struct relay_index *_index;
+
+ DBG2("Adding relay index with stream id %" PRIu64 " and seqnum %" PRIu64,
+ stream->stream_handle, index->index_n.key);
+
+ node_ptr = cds_lfht_add_unique(stream->indexes_ht->ht,
+ stream->indexes_ht->hash_fct(&index->index_n, lttng_ht_seed),
+ stream->indexes_ht->match_fct, &index->index_n,
+ &index->index_n.node);
+ if (node_ptr != &index->index_n.node) {
+ _index = caa_container_of(node_ptr, struct relay_index,
+ index_n.node);
+ } else {
+ _index = NULL;
+ }
+ return _index;
+}
+
+/*
+ * Should be called with RCU read-side lock held.
+ */
+static bool relay_index_get(struct relay_index *index)
+{
+ DBG2("index get for stream id %" PRIu64 " and seqnum %" PRIu64 " refcount %d",
+ index->stream->stream_handle, index->index_n.key,
+ (int) index->ref.refcount);
+
+ return urcu_ref_get_unless_zero(&index->ref);
+}
+
+/*
+ * Get a relayd index in within the given stream, or create it if not
+ * present.
+ *
+ * Called with stream mutex held.
+ * Return index object or else NULL on error.
+ */
+struct relay_index *relay_index_get_by_id_or_create(struct relay_stream *stream,
+ uint64_t net_seq_num)
+{
+ struct lttng_ht_node_u64 *node;
+ struct lttng_ht_iter iter;
+ struct relay_index *index = NULL;
+
+ DBG3("Finding index for stream id %" PRIu64 " and seq_num %" PRIu64,
+ stream->stream_handle, net_seq_num);
+
+ rcu_read_lock();
+ lttng_ht_lookup(stream->indexes_ht, &net_seq_num, &iter);
+ node = lttng_ht_iter_get_node_u64(&iter);
+ if (node) {
+ index = caa_container_of(node, struct relay_index, index_n);
+ } else {
+ struct relay_index *oldindex;
+
+ index = relay_index_create(stream, net_seq_num);
+ if (!index) {
+ ERR("Cannot create index for stream id %" PRIu64 " and seq_num %" PRIu64,
+ stream->stream_handle, net_seq_num);
+ goto end;
+ }
+ oldindex = relay_index_add_unique(stream, index);
+ if (oldindex) {
+ /* Added concurrently, keep old. */
+ relay_index_put(index);
+ index = oldindex;
+ if (!relay_index_get(index)) {
+ index = NULL;
+ }
+ } else {
+ stream->indexes_in_flight++;
+ index->in_hash_table = true;
+ }
+ }
+end:
+ rcu_read_unlock();
+ DBG2("Index %sfound or created in HT for stream ID %" PRIu64 " and seqnum %" PRIu64,
+ (index == NULL) ? "NOT " : "", stream->stream_handle, net_seq_num);
+ return index;
+}
+
+int relay_index_set_file(struct relay_index *index,
+ struct lttng_index_file *index_file,
+ uint64_t data_offset)
+{
+ int ret = 0;
+
+ pthread_mutex_lock(&index->lock);
+ if (index->index_file) {
+ ret = -1;
+ goto end;
+ }
+ lttng_index_file_get(index_file);
+ index->index_file = index_file;
+ index->index_data.offset = data_offset;
+end:
+ pthread_mutex_unlock(&index->lock);
+ return ret;
+}
+
+int relay_index_set_data(struct relay_index *index,
+ const struct ctf_packet_index *data)
+{
+ int ret = 0;
+
+ pthread_mutex_lock(&index->lock);
+ if (index->has_index_data) {
+ ret = -1;
+ goto end;
+ }
+ /* Set everything except data_offset. */
+ index->index_data.packet_size = data->packet_size;
+ index->index_data.content_size = data->content_size;
+ index->index_data.timestamp_begin = data->timestamp_begin;
+ index->index_data.timestamp_end = data->timestamp_end;
+ index->index_data.events_discarded = data->events_discarded;
+ index->index_data.stream_id = data->stream_id;
+ index->has_index_data = true;
+end:
+ pthread_mutex_unlock(&index->lock);
+ return ret;
+}
+
+static void index_destroy(struct relay_index *index)
+{
+ free(index);
+}
+
+static void index_destroy_rcu(struct rcu_head *rcu_head)
+{
+ struct relay_index *index =
+ caa_container_of(rcu_head, struct relay_index, rcu_node);
+
+ index_destroy(index);
+}
+
+/* Stream lock must be held by the caller. */
+static void index_release(struct urcu_ref *ref)
+{
+ struct relay_index *index = caa_container_of(ref, struct relay_index, ref);
+ struct relay_stream *stream = index->stream;
+ int ret;
+ struct lttng_ht_iter iter;
+
+ if (index->index_file) {
+ lttng_index_file_put(index->index_file);
+ index->index_file = NULL;
+ }
+ if (index->in_hash_table) {
+ /* Delete index from hash table. */
+ iter.iter.node = &index->index_n.node;
+ ret = lttng_ht_del(stream->indexes_ht, &iter);
+ LTTNG_ASSERT(!ret);
+ stream->indexes_in_flight--;
+ }
+
+ stream_put(index->stream);
+ index->stream = NULL;
+
+ call_rcu(&index->rcu_node, index_destroy_rcu);
+}
+
+/*
+ * Called with stream mutex held.
+ *
+ * Stream lock must be held by the caller.
+ */
+void relay_index_put(struct relay_index *index)
+{
+ DBG2("index put for stream id %" PRIu64 " and seqnum %" PRIu64 " refcount %d",
+ index->stream->stream_handle, index->index_n.key,
+ (int) index->ref.refcount);
+ /*
+ * Ensure existance of index->lock for index unlock.
+ */
+ rcu_read_lock();
+ /*
+ * Index lock ensures that concurrent test and update of stream
+ * ref is atomic.
+ */
+ LTTNG_ASSERT(index->ref.refcount != 0);
+ urcu_ref_put(&index->ref, index_release);
+ rcu_read_unlock();
+}
+
+/*
+ * Try to flush index to disk. Releases self-reference to index once
+ * flush succeeds.
+ *
+ * Stream lock must be held by the caller.
+ * Return 0 on successful flush, a negative value on error, or positive
+ * value if no flush was performed.
+ */
+int relay_index_try_flush(struct relay_index *index)
+{
+ int ret = 1;
+ bool flushed = false;
+
+ pthread_mutex_lock(&index->lock);
+ if (index->flushed) {
+ goto skip;
+ }
+ /* Check if we are ready to flush. */
+ if (!index->has_index_data || !index->index_file) {
+ goto skip;
+ }
+
+ DBG2("Writing index for stream ID %" PRIu64 " and seq num %" PRIu64,
+ index->stream->stream_handle, index->index_n.key);
+ flushed = true;
+ index->flushed = true;
+ ret = lttng_index_file_write(index->index_file, &index->index_data);
+skip:
+ pthread_mutex_unlock(&index->lock);
+
+ if (flushed) {
+ /* Put self-ref from index now that it has been flushed. */
+ relay_index_put(index);
+ }
+ return ret;
+}
+
+/*
+ * Close every relay index within a given stream, without flushing
+ * them.
+ */
+void relay_index_close_all(struct relay_stream *stream)
+{
+ struct lttng_ht_iter iter;
+ struct relay_index *index;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
+ index, index_n.node) {
+ /* Put self-ref from index. */
+ relay_index_put(index);
+ }
+ rcu_read_unlock();
+}
+
+void relay_index_close_partial_fd(struct relay_stream *stream)
+{
+ struct lttng_ht_iter iter;
+ struct relay_index *index;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
+ index, index_n.node) {
+ if (!index->index_file) {
+ continue;
+ }
+ /*
+ * Partial index has its index_file: we have only
+ * received its info from the data socket.
+ * Put self-ref from index.
+ */
+ relay_index_put(index);
+ }
+ rcu_read_unlock();
+}
+
+uint64_t relay_index_find_last(struct relay_stream *stream)
+{
+ struct lttng_ht_iter iter;
+ struct relay_index *index;
+ uint64_t net_seq_num = -1ULL;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
+ index, index_n.node) {
+ if (net_seq_num == -1ULL ||
+ index->index_n.key > net_seq_num) {
+ net_seq_num = index->index_n.key;
+ }
+ }
+ rcu_read_unlock();
+ return net_seq_num;
+}
+
+/*
+ * Update the index file of an already existing relay_index.
+ * Offsets by 'removed_data_count' the offset field of an index.
+ */
+static
+int relay_index_switch_file(struct relay_index *index,
+ struct lttng_index_file *new_index_file,
+ uint64_t removed_data_count)
+{
+ int ret = 0;
+ uint64_t offset;
+
+ pthread_mutex_lock(&index->lock);
+ if (!index->index_file) {
+ ERR("No index_file");
+ ret = 0;
+ goto end;
+ }
+
+ lttng_index_file_put(index->index_file);
+ lttng_index_file_get(new_index_file);
+ index->index_file = new_index_file;
+ offset = be64toh(index->index_data.offset);
+ index->index_data.offset = htobe64(offset - removed_data_count);
+
+end:
+ pthread_mutex_unlock(&index->lock);
+ return ret;
+}
+
+/*
+ * Switch the index file of all pending indexes for a stream and update the
+ * data offset by substracting the last safe position.
+ * Stream lock must be held.
+ */
+int relay_index_switch_all_files(struct relay_stream *stream)
+{
+ struct lttng_ht_iter iter;
+ struct relay_index *index;
+ int ret = 0;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter,
+ index, index_n.node) {
+ ret = relay_index_switch_file(index, stream->index_file,
+ stream->pos_after_last_complete_data_index);
+ if (ret) {
+ goto end;
+ }
+ }
+end:
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Set index data from the control port to a given index object.
+ */
+int relay_index_set_control_data(struct relay_index *index,
+ const struct lttcomm_relayd_index *data,
+ unsigned int minor_version)
+{
+ /* The index on disk is encoded in big endian. */
+ ctf_packet_index index_data {};
+
+ index_data.packet_size = htobe64(data->packet_size);
+ index_data.content_size = htobe64(data->content_size);
+ index_data.timestamp_begin = htobe64(data->timestamp_begin);
+ index_data.timestamp_end = htobe64(data->timestamp_end);
+ index_data.events_discarded = htobe64(data->events_discarded);
+ index_data.stream_id = htobe64(data->stream_id);
+
+ if (minor_version >= 8) {
+ index->index_data.stream_instance_id = htobe64(data->stream_instance_id);
+ index->index_data.packet_seq_num = htobe64(data->packet_seq_num);
+ } else {
+ uint64_t unset_value = -1ULL;
+
+ index->index_data.stream_instance_id = htobe64(unset_value);
+ index->index_data.packet_seq_num = htobe64(unset_value);
+ }
+
+ return relay_index_set_data(index, &index_data);
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <fcntl.h>
-#include <getopt.h>
-#include <grp.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/mount.h>
-#include <sys/resource.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <urcu/futex.h>
-#include <urcu/rculist.h>
-#include <urcu/uatomic.h>
-
-#include <common/common.h>
-#include <common/compat/endian.h>
-#include <common/compat/poll.h>
-#include <common/compat/socket.h>
-#include <common/defaults.h>
-#include <common/fd-tracker/utils.h>
-#include <common/fs-handle.h>
-#include <common/futex.h>
-#include <common/index/index.h>
-#include <common/sessiond-comm/inet.h>
-#include <common/sessiond-comm/relayd.h>
-#include <common/sessiond-comm/sessiond-comm.h>
-#include <common/uri.h>
-#include <common/utils.h>
-#include <lttng/lttng.h>
-
-#include "cmd.h"
-#include "connection.h"
-#include "ctf-trace.h"
-#include "health-relayd.h"
-#include "live.h"
-#include "lttng-relayd.h"
-#include "session.h"
-#include "stream.h"
-#include "testpoint.h"
-#include "utils.h"
-#include "viewer-session.h"
-#include "viewer-stream.h"
-
-#define SESSION_BUF_DEFAULT_COUNT 16
-
-static struct lttng_uri *live_uri;
-
-/*
- * This pipe is used to inform the worker thread that a command is queued and
- * ready to be processed.
- */
-static int live_conn_pipe[2] = { -1, -1 };
-
-/* Shared between threads */
-static int live_dispatch_thread_exit;
-
-static pthread_t live_listener_thread;
-static pthread_t live_dispatcher_thread;
-static pthread_t live_worker_thread;
-
-/*
- * Relay command queue.
- *
- * The live_thread_listener and live_thread_dispatcher communicate with this
- * queue.
- */
-static struct relay_conn_queue viewer_conn_queue;
-
-static uint64_t last_relay_viewer_session_id;
-static pthread_mutex_t last_relay_viewer_session_id_lock =
- PTHREAD_MUTEX_INITIALIZER;
-
-/*
- * Cleanup the daemon
- */
-static
-void cleanup_relayd_live(void)
-{
- DBG("Cleaning up");
-
- free(live_uri);
-}
-
-/*
- * Receive a request buffer using a given socket, destination allocated buffer
- * of length size.
- *
- * Return the size of the received message or else a negative value on error
- * with errno being set by recvmsg() syscall.
- */
-static
-ssize_t recv_request(struct lttcomm_sock *sock, void *buf, size_t size)
-{
- ssize_t ret;
-
- ret = sock->ops->recvmsg(sock, buf, size, 0);
- if (ret < 0 || ret != size) {
- if (ret == 0) {
- /* Orderly shutdown. Not necessary to print an error. */
- DBG("Socket %d did an orderly shutdown", sock->fd);
- } else {
- ERR("Relay failed to receive request.");
- }
- ret = -1;
- }
-
- return ret;
-}
-
-/*
- * Send a response buffer using a given socket, source allocated buffer of
- * length size.
- *
- * Return the size of the sent message or else a negative value on error with
- * errno being set by sendmsg() syscall.
- */
-static
-ssize_t send_response(struct lttcomm_sock *sock, void *buf, size_t size)
-{
- ssize_t ret;
-
- ret = sock->ops->sendmsg(sock, buf, size, 0);
- if (ret < 0) {
- ERR("Relayd failed to send response.");
- }
-
- return ret;
-}
-
-/*
- * Atomically check if new streams got added in one of the sessions attached
- * and reset the flag to 0.
- *
- * Returns 1 if new streams got added, 0 if nothing changed, a negative value
- * on error.
- */
-static
-int check_new_streams(struct relay_connection *conn)
-{
- struct relay_session *session;
- unsigned long current_val;
- int ret = 0;
-
- if (!conn->viewer_session) {
- goto end;
- }
- rcu_read_lock();
- cds_list_for_each_entry_rcu(session,
- &conn->viewer_session->session_list,
- viewer_session_node) {
- if (!session_get(session)) {
- continue;
- }
- current_val = uatomic_cmpxchg(&session->new_streams, 1, 0);
- ret = current_val;
- session_put(session);
- if (ret == 1) {
- goto end;
- }
- }
-end:
- rcu_read_unlock();
- return ret;
-}
-
-/*
- * Send viewer streams to the given socket. The ignore_sent_flag indicates if
- * this function should ignore the sent flag or not.
- *
- * Return 0 on success or else a negative value.
- */
-static
-ssize_t send_viewer_streams(struct lttcomm_sock *sock,
- uint64_t session_id, unsigned int ignore_sent_flag)
-{
- ssize_t ret;
- struct lttng_ht_iter iter;
- struct relay_viewer_stream *vstream;
-
- rcu_read_lock();
-
- cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter, vstream,
- stream_n.node) {
- struct ctf_trace *ctf_trace;
- struct lttng_viewer_stream send_stream = {};
-
- health_code_update();
-
- if (!viewer_stream_get(vstream)) {
- continue;
- }
-
- pthread_mutex_lock(&vstream->stream->lock);
- /* Ignore if not the same session. */
- if (vstream->stream->trace->session->id != session_id ||
- (!ignore_sent_flag && vstream->sent_flag)) {
- pthread_mutex_unlock(&vstream->stream->lock);
- viewer_stream_put(vstream);
- continue;
- }
-
- ctf_trace = vstream->stream->trace;
- send_stream.id = htobe64(vstream->stream->stream_handle);
- send_stream.ctf_trace_id = htobe64(ctf_trace->id);
- send_stream.metadata_flag = htobe32(
- vstream->stream->is_metadata);
- if (lttng_strncpy(send_stream.path_name, vstream->path_name,
- sizeof(send_stream.path_name))) {
- pthread_mutex_unlock(&vstream->stream->lock);
- viewer_stream_put(vstream);
- ret = -1; /* Error. */
- goto end_unlock;
- }
- if (lttng_strncpy(send_stream.channel_name,
- vstream->channel_name,
- sizeof(send_stream.channel_name))) {
- pthread_mutex_unlock(&vstream->stream->lock);
- viewer_stream_put(vstream);
- ret = -1; /* Error. */
- goto end_unlock;
- }
-
- DBG("Sending stream %" PRIu64 " to viewer",
- vstream->stream->stream_handle);
- vstream->sent_flag = 1;
- pthread_mutex_unlock(&vstream->stream->lock);
-
- ret = send_response(sock, &send_stream, sizeof(send_stream));
- viewer_stream_put(vstream);
- if (ret < 0) {
- goto end_unlock;
- }
- }
-
- ret = 0;
-
-end_unlock:
- rcu_read_unlock();
- return ret;
-}
-
-/*
- * Create every viewer stream possible for the given session with the seek
- * type. Three counters *can* be return which are in order the total amount of
- * viewer stream of the session, the number of unsent stream and the number of
- * stream created. Those counters can be NULL and thus will be ignored.
- *
- * session must be locked to ensure that we see either none or all initial
- * streams for a session, but no intermediate state..
- *
- * Return 0 on success or else a negative value.
- */
-static int make_viewer_streams(struct relay_session *relay_session,
- struct relay_viewer_session *viewer_session,
- enum lttng_viewer_seek seek_t,
- uint32_t *nb_total,
- uint32_t *nb_unsent,
- uint32_t *nb_created,
- bool *closed)
-{
- int ret;
- struct lttng_ht_iter iter;
- struct ctf_trace *ctf_trace;
- struct relay_stream *relay_stream = NULL;
-
- LTTNG_ASSERT(relay_session);
- ASSERT_LOCKED(relay_session->lock);
-
- if (relay_session->connection_closed) {
- *closed = true;
- }
-
- /*
- * Create viewer streams for relay streams that are ready to be
- * used for a the given session id only.
- */
- rcu_read_lock();
- cds_lfht_for_each_entry (relay_session->ctf_traces_ht->ht, &iter.iter,
- ctf_trace, node.node) {
- bool trace_has_metadata_stream = false;
-
- health_code_update();
-
- if (!ctf_trace_get(ctf_trace)) {
- continue;
- }
-
- /*
- * Iterate over all the streams of the trace to see if we have a
- * metadata stream.
- */
- cds_list_for_each_entry_rcu(relay_stream,
- &ctf_trace->stream_list, stream_node)
- {
- bool is_metadata_stream;
-
- pthread_mutex_lock(&relay_stream->lock);
- is_metadata_stream = relay_stream->is_metadata;
- pthread_mutex_unlock(&relay_stream->lock);
-
- if (is_metadata_stream) {
- trace_has_metadata_stream = true;
- break;
- }
- }
-
- relay_stream = NULL;
-
- /*
- * If there is no metadata stream in this trace at the moment
- * and we never sent one to the viewer, skip the trace. We
- * accept that the viewer will not see this trace at all.
- */
- if (!trace_has_metadata_stream &&
- !ctf_trace->metadata_stream_sent_to_viewer) {
- ctf_trace_put(ctf_trace);
- continue;
- }
-
- cds_list_for_each_entry_rcu(relay_stream,
- &ctf_trace->stream_list, stream_node)
- {
- struct relay_viewer_stream *viewer_stream;
-
- if (!stream_get(relay_stream)) {
- continue;
- }
-
- pthread_mutex_lock(&relay_stream->lock);
- /*
- * stream published is protected by the session lock.
- */
- if (!relay_stream->published) {
- goto next;
- }
- viewer_stream = viewer_stream_get_by_id(
- relay_stream->stream_handle);
- if (!viewer_stream) {
- struct lttng_trace_chunk *viewer_stream_trace_chunk = NULL;
-
- /*
- * Save that we sent the metadata stream to the
- * viewer. So that we know what trace the viewer
- * is aware of.
- */
- if (relay_stream->is_metadata) {
- ctf_trace->metadata_stream_sent_to_viewer = true;
- }
-
- /*
- * If a rotation is ongoing, use a copy of the
- * relay stream's chunk to ensure the stream
- * files exist.
- *
- * Otherwise, the viewer session's current trace
- * chunk can be used safely.
- */
- if ((relay_stream->ongoing_rotation.is_set ||
- relay_session->ongoing_rotation) &&
- relay_stream->trace_chunk) {
- viewer_stream_trace_chunk = lttng_trace_chunk_copy(
- relay_stream->trace_chunk);
- if (!viewer_stream_trace_chunk) {
- ret = -1;
- ctf_trace_put(ctf_trace);
- goto error_unlock;
- }
- } else {
- /*
- * Transition the viewer session into the newest trace chunk available.
- */
- if (!lttng_trace_chunk_ids_equal(viewer_session->current_trace_chunk,
- relay_stream->trace_chunk)) {
-
- ret = viewer_session_set_trace_chunk_copy(
- viewer_session,
- relay_stream->trace_chunk);
- if (ret) {
- ret = -1;
- ctf_trace_put(ctf_trace);
- goto error_unlock;
- }
- }
-
- if (relay_stream->trace_chunk) {
- /*
- * If the corresponding relay
- * stream's trace chunk is set,
- * the viewer stream will be
- * created under it.
- *
- * Note that a relay stream can
- * have a NULL output trace
- * chunk (for instance, after a
- * clear against a stopped
- * session).
- */
- const bool reference_acquired = lttng_trace_chunk_get(
- viewer_session->current_trace_chunk);
-
- LTTNG_ASSERT(reference_acquired);
- viewer_stream_trace_chunk =
- viewer_session->current_trace_chunk;
- }
- }
-
- viewer_stream = viewer_stream_create(
- relay_stream,
- viewer_stream_trace_chunk,
- seek_t);
- lttng_trace_chunk_put(viewer_stream_trace_chunk);
- viewer_stream_trace_chunk = NULL;
- if (!viewer_stream) {
- ret = -1;
- ctf_trace_put(ctf_trace);
- goto error_unlock;
- }
-
- if (nb_created) {
- /* Update number of created stream counter. */
- (*nb_created)++;
- }
- /*
- * Ensure a self-reference is preserved even
- * after we have put our local reference.
- */
- if (!viewer_stream_get(viewer_stream)) {
- ERR("Unable to get self-reference on viewer stream, logic error.");
- abort();
- }
- } else {
- if (!viewer_stream->sent_flag && nb_unsent) {
- /* Update number of unsent stream counter. */
- (*nb_unsent)++;
- }
- }
- /* Update number of total stream counter. */
- if (nb_total) {
- if (relay_stream->is_metadata) {
- if (!relay_stream->closed ||
- relay_stream->metadata_received >
- viewer_stream->metadata_sent) {
- (*nb_total)++;
- }
- } else {
- if (!relay_stream->closed ||
- !(((int64_t)(relay_stream->prev_data_seq -
- relay_stream->last_net_seq_num)) >=
- 0)) {
- (*nb_total)++;
- }
- }
- }
- /* Put local reference. */
- viewer_stream_put(viewer_stream);
- next:
- pthread_mutex_unlock(&relay_stream->lock);
- stream_put(relay_stream);
- }
- relay_stream = NULL;
- ctf_trace_put(ctf_trace);
- }
-
- ret = 0;
-
-error_unlock:
- rcu_read_unlock();
-
- if (relay_stream) {
- pthread_mutex_unlock(&relay_stream->lock);
- stream_put(relay_stream);
- }
-
- return ret;
-}
-
-int relayd_live_stop(void)
-{
- /* Stop dispatch thread */
- CMM_STORE_SHARED(live_dispatch_thread_exit, 1);
- futex_nto1_wake(&viewer_conn_queue.futex);
- return 0;
-}
-
-/*
- * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set.
- */
-static
-int create_named_thread_poll_set(struct lttng_poll_event *events,
- int size, const char *name)
-{
- int ret;
-
- if (events == NULL || size == 0) {
- ret = -1;
- goto error;
- }
-
- ret = fd_tracker_util_poll_create(the_fd_tracker,
- name, events, 1, LTTNG_CLOEXEC);
- if (ret) {
- PERROR("Failed to create \"%s\" poll file descriptor", name);
- goto error;
- }
-
- /* Add quit pipe */
- ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR);
- if (ret < 0) {
- goto error;
- }
-
- return 0;
-
-error:
- return ret;
-}
-
-/*
- * Check if the thread quit pipe was triggered.
- *
- * Return 1 if it was triggered else 0;
- */
-static
-int check_thread_quit_pipe(int fd, uint32_t events)
-{
- if (fd == thread_quit_pipe[0] && (events & LPOLLIN)) {
- return 1;
- }
-
- return 0;
-}
-
-static
-int create_sock(void *data, int *out_fd)
-{
- int ret;
- struct lttcomm_sock *sock = data;
-
- ret = lttcomm_create_sock(sock);
- if (ret < 0) {
- goto end;
- }
-
- *out_fd = sock->fd;
-end:
- return ret;
-}
-
-static
-int close_sock(void *data, int *in_fd)
-{
- struct lttcomm_sock *sock = data;
-
- return sock->ops->close(sock);
-}
-
-static int accept_sock(void *data, int *out_fd)
-{
- int ret = 0;
- /* Socks is an array of in_sock, out_sock. */
- struct lttcomm_sock **socks = data;
- struct lttcomm_sock *in_sock = socks[0];
-
- socks[1] = in_sock->ops->accept(in_sock);
- if (!socks[1]) {
- ret = -1;
- goto end;
- }
- *out_fd = socks[1]->fd;
-end:
- return ret;
-}
-
-static
-struct lttcomm_sock *accept_live_sock(struct lttcomm_sock *listening_sock,
- const char *name)
-{
- int out_fd, ret;
- struct lttcomm_sock *socks[2] = { listening_sock, NULL };
- struct lttcomm_sock *new_sock = NULL;
-
- ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &out_fd,
- (const char **) &name, 1, accept_sock, &socks);
- if (ret) {
- goto end;
- }
- new_sock = socks[1];
- DBG("%s accepted, socket %d", name, new_sock->fd);
-end:
- return new_sock;
-}
-
-/*
- * Create and init socket from uri.
- */
-static
-struct lttcomm_sock *init_socket(struct lttng_uri *uri, const char *name)
-{
- int ret, sock_fd;
- struct lttcomm_sock *sock = NULL;
- char uri_str[LTTNG_PATH_MAX];
- char *formated_name = NULL;
-
- sock = lttcomm_alloc_sock_from_uri(uri);
- if (sock == NULL) {
- ERR("Allocating socket");
- goto error;
- }
-
- /*
- * Don't fail to create the socket if the name can't be built as it is
- * only used for debugging purposes.
- */
- ret = uri_to_str_url(uri, uri_str, sizeof(uri_str));
- uri_str[sizeof(uri_str) - 1] = '\0';
- if (ret >= 0) {
- ret = asprintf(&formated_name, "%s socket @ %s", name,
- uri_str);
- if (ret < 0) {
- formated_name = NULL;
- }
- }
-
- ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd,
- (const char **) (formated_name ? &formated_name : NULL),
- 1, create_sock, sock);
- if (ret) {
- PERROR("Failed to create \"%s\" socket",
- formated_name ?: "Unknown");
- goto error;
- }
- DBG("Listening on %s socket %d", name, sock->fd);
-
- ret = sock->ops->bind(sock);
- if (ret < 0) {
- PERROR("Failed to bind lttng-live socket");
- goto error;
- }
-
- ret = sock->ops->listen(sock, -1);
- if (ret < 0) {
- goto error;
-
- }
-
- free(formated_name);
- return sock;
-
-error:
- if (sock) {
- lttcomm_destroy_sock(sock);
- }
- free(formated_name);
- return NULL;
-}
-
-/*
- * This thread manages the listening for new connections on the network
- */
-static
-void *thread_listener(void *data)
-{
- int i, ret, pollfd, err = -1;
- uint32_t revents, nb_fd;
- struct lttng_poll_event events;
- struct lttcomm_sock *live_control_sock;
-
- DBG("[thread] Relay live listener started");
-
- rcu_register_thread();
- health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_LISTENER);
-
- health_code_update();
-
- live_control_sock = init_socket(live_uri, "Live listener");
- if (!live_control_sock) {
- goto error_sock_control;
- }
-
- /* Pass 2 as size here for the thread quit pipe and control sockets. */
- ret = create_named_thread_poll_set(&events, 2,
- "Live listener thread epoll");
- if (ret < 0) {
- goto error_create_poll;
- }
-
- /* Add the control socket */
- ret = lttng_poll_add(&events, live_control_sock->fd, LPOLLIN | LPOLLRDHUP);
- if (ret < 0) {
- goto error_poll_add;
- }
-
- lttng_relay_notify_ready();
-
- if (testpoint(relayd_thread_live_listener)) {
- goto error_testpoint;
- }
-
- while (1) {
- health_code_update();
-
- DBG("Listener accepting live viewers connections");
-
-restart:
- health_poll_entry();
- ret = lttng_poll_wait(&events, -1);
- health_poll_exit();
- if (ret < 0) {
- /*
- * Restart interrupted system call.
- */
- if (errno == EINTR) {
- goto restart;
- }
- goto error;
- }
- nb_fd = ret;
-
- DBG("Relay new viewer connection received");
- for (i = 0; i < nb_fd; i++) {
- health_code_update();
-
- /* Fetch once the poll data */
- revents = LTTNG_POLL_GETEV(&events, i);
- pollfd = LTTNG_POLL_GETFD(&events, i);
-
- /* Thread quit pipe has been closed. Killing thread. */
- ret = check_thread_quit_pipe(pollfd, revents);
- if (ret) {
- err = 0;
- goto exit;
- }
-
- if (revents & LPOLLIN) {
- /*
- * A new connection is requested, therefore a
- * viewer connection is allocated in this
- * thread, enqueued to a global queue and
- * dequeued (and freed) in the worker thread.
- */
- int val = 1;
- struct relay_connection *new_conn;
- struct lttcomm_sock *newsock;
-
- newsock = accept_live_sock(live_control_sock,
- "Live socket to client");
- if (!newsock) {
- PERROR("accepting control sock");
- goto error;
- }
- DBG("Relay viewer connection accepted socket %d", newsock->fd);
-
- ret = setsockopt(newsock->fd, SOL_SOCKET, SO_REUSEADDR, &val,
- sizeof(val));
- if (ret < 0) {
- PERROR("setsockopt inet");
- lttcomm_destroy_sock(newsock);
- goto error;
- }
- new_conn = connection_create(newsock, RELAY_CONNECTION_UNKNOWN);
- if (!new_conn) {
- lttcomm_destroy_sock(newsock);
- goto error;
- }
- /* Ownership assumed by the connection. */
- newsock = NULL;
-
- /* Enqueue request for the dispatcher thread. */
- cds_wfcq_enqueue(&viewer_conn_queue.head, &viewer_conn_queue.tail,
- &new_conn->qnode);
-
- /*
- * Wake the dispatch queue futex.
- * Implicit memory barrier with the
- * exchange in cds_wfcq_enqueue.
- */
- futex_nto1_wake(&viewer_conn_queue.futex);
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- ERR("socket poll error");
- goto error;
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- goto error;
- }
- }
- }
-
-exit:
-error:
-error_poll_add:
-error_testpoint:
- (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
-error_create_poll:
- if (live_control_sock->fd >= 0) {
- int sock_fd = live_control_sock->fd;
-
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- &sock_fd, 1, close_sock,
- live_control_sock);
- if (ret) {
- PERROR("close");
- }
- live_control_sock->fd = -1;
- }
- lttcomm_destroy_sock(live_control_sock);
-error_sock_control:
- if (err) {
- health_error();
- DBG("Live viewer listener thread exited with error");
- }
- health_unregister(health_relayd);
- rcu_unregister_thread();
- DBG("Live viewer listener thread cleanup complete");
- if (lttng_relay_stop_threads()) {
- ERR("Error stopping threads");
- }
- return NULL;
-}
-
-/*
- * This thread manages the dispatching of the requests to worker threads
- */
-static
-void *thread_dispatcher(void *data)
-{
- int err = -1;
- ssize_t ret;
- struct cds_wfcq_node *node;
- struct relay_connection *conn = NULL;
-
- DBG("[thread] Live viewer relay dispatcher started");
-
- health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_DISPATCHER);
-
- if (testpoint(relayd_thread_live_dispatcher)) {
- goto error_testpoint;
- }
-
- health_code_update();
-
- for (;;) {
- health_code_update();
-
- /* Atomically prepare the queue futex */
- futex_nto1_prepare(&viewer_conn_queue.futex);
-
- if (CMM_LOAD_SHARED(live_dispatch_thread_exit)) {
- break;
- }
-
- do {
- health_code_update();
-
- /* Dequeue commands */
- node = cds_wfcq_dequeue_blocking(&viewer_conn_queue.head,
- &viewer_conn_queue.tail);
- if (node == NULL) {
- DBG("Woken up but nothing in the live-viewer "
- "relay command queue");
- /* Continue thread execution */
- break;
- }
- conn = caa_container_of(node, struct relay_connection, qnode);
- DBG("Dispatching viewer request waiting on sock %d",
- conn->sock->fd);
-
- /*
- * Inform worker thread of the new request. This
- * call is blocking so we can be assured that
- * the data will be read at some point in time
- * or wait to the end of the world :)
- */
- ret = lttng_write(live_conn_pipe[1], &conn, sizeof(conn));
- if (ret < 0) {
- PERROR("write conn pipe");
- connection_put(conn);
- goto error;
- }
- } while (node != NULL);
-
- /* Futex wait on queue. Blocking call on futex() */
- health_poll_entry();
- futex_nto1_wait(&viewer_conn_queue.futex);
- health_poll_exit();
- }
-
- /* Normal exit, no error */
- err = 0;
-
-error:
-error_testpoint:
- if (err) {
- health_error();
- ERR("Health error occurred in %s", __func__);
- }
- health_unregister(health_relayd);
- DBG("Live viewer dispatch thread dying");
- if (lttng_relay_stop_threads()) {
- ERR("Error stopping threads");
- }
- return NULL;
-}
-
-/*
- * Establish connection with the viewer and check the versions.
- *
- * Return 0 on success or else negative value.
- */
-static
-int viewer_connect(struct relay_connection *conn)
-{
- int ret;
- struct lttng_viewer_connect reply, msg;
-
- conn->version_check_done = 1;
-
- health_code_update();
-
- DBG("Viewer is establishing a connection to the relayd.");
-
- ret = recv_request(conn->sock, &msg, sizeof(msg));
- if (ret < 0) {
- goto end;
- }
-
- health_code_update();
-
- memset(&reply, 0, sizeof(reply));
- reply.major = RELAYD_VERSION_COMM_MAJOR;
- reply.minor = RELAYD_VERSION_COMM_MINOR;
-
- /* Major versions must be the same */
- if (reply.major != be32toh(msg.major)) {
- DBG("Incompatible major versions ([relayd] %u vs [client] %u)",
- reply.major, be32toh(msg.major));
- ret = -1;
- goto end;
- }
-
- conn->major = reply.major;
- /* We adapt to the lowest compatible version */
- if (reply.minor <= be32toh(msg.minor)) {
- conn->minor = reply.minor;
- } else {
- conn->minor = be32toh(msg.minor);
- }
-
- if (be32toh(msg.type) == LTTNG_VIEWER_CLIENT_COMMAND) {
- conn->type = RELAY_VIEWER_COMMAND;
- } else if (be32toh(msg.type) == LTTNG_VIEWER_CLIENT_NOTIFICATION) {
- conn->type = RELAY_VIEWER_NOTIFICATION;
- } else {
- ERR("Unknown connection type : %u", be32toh(msg.type));
- ret = -1;
- goto end;
- }
-
- reply.major = htobe32(reply.major);
- reply.minor = htobe32(reply.minor);
- if (conn->type == RELAY_VIEWER_COMMAND) {
- /*
- * Increment outside of htobe64 macro, because the argument can
- * be used more than once within the macro, and thus the
- * operation may be undefined.
- */
- pthread_mutex_lock(&last_relay_viewer_session_id_lock);
- last_relay_viewer_session_id++;
- pthread_mutex_unlock(&last_relay_viewer_session_id_lock);
- reply.viewer_session_id = htobe64(last_relay_viewer_session_id);
- }
-
- health_code_update();
-
- ret = send_response(conn->sock, &reply, sizeof(reply));
- if (ret < 0) {
- goto end;
- }
-
- health_code_update();
-
- DBG("Version check done using protocol %u.%u", conn->major, conn->minor);
- ret = 0;
-
-end:
- return ret;
-}
-
-/*
- * Send the viewer the list of current sessions.
- * We need to create a copy of the hash table content because otherwise
- * we cannot assume the number of entries stays the same between getting
- * the number of HT elements and iteration over the HT.
- *
- * Return 0 on success or else a negative value.
- */
-static
-int viewer_list_sessions(struct relay_connection *conn)
-{
- int ret = 0;
- struct lttng_viewer_list_sessions session_list;
- struct lttng_ht_iter iter;
- struct relay_session *session;
- struct lttng_viewer_session *send_session_buf = NULL;
- uint32_t buf_count = SESSION_BUF_DEFAULT_COUNT;
- uint32_t count = 0;
-
- DBG("List sessions received");
-
- send_session_buf = zmalloc(SESSION_BUF_DEFAULT_COUNT * sizeof(*send_session_buf));
- if (!send_session_buf) {
- return -1;
- }
-
- rcu_read_lock();
- cds_lfht_for_each_entry(sessions_ht->ht, &iter.iter, session,
- session_n.node) {
- struct lttng_viewer_session *send_session;
-
- health_code_update();
-
- pthread_mutex_lock(&session->lock);
- if (session->connection_closed) {
- /* Skip closed session */
- goto next_session;
- }
-
- if (count >= buf_count) {
- struct lttng_viewer_session *newbuf;
- uint32_t new_buf_count = buf_count << 1;
-
- newbuf = realloc(send_session_buf,
- new_buf_count * sizeof(*send_session_buf));
- if (!newbuf) {
- ret = -1;
- goto break_loop;
- }
- send_session_buf = newbuf;
- buf_count = new_buf_count;
- }
- send_session = &send_session_buf[count];
- if (lttng_strncpy(send_session->session_name,
- session->session_name,
- sizeof(send_session->session_name))) {
- ret = -1;
- goto break_loop;
- }
- if (lttng_strncpy(send_session->hostname, session->hostname,
- sizeof(send_session->hostname))) {
- ret = -1;
- goto break_loop;
- }
- send_session->id = htobe64(session->id);
- send_session->live_timer = htobe32(session->live_timer);
- if (session->viewer_attached) {
- send_session->clients = htobe32(1);
- } else {
- send_session->clients = htobe32(0);
- }
- send_session->streams = htobe32(session->stream_count);
- count++;
- next_session:
- pthread_mutex_unlock(&session->lock);
- continue;
- break_loop:
- pthread_mutex_unlock(&session->lock);
- break;
- }
- rcu_read_unlock();
- if (ret < 0) {
- goto end_free;
- }
-
- session_list.sessions_count = htobe32(count);
-
- health_code_update();
-
- ret = send_response(conn->sock, &session_list, sizeof(session_list));
- if (ret < 0) {
- goto end_free;
- }
-
- health_code_update();
-
- ret = send_response(conn->sock, send_session_buf,
- count * sizeof(*send_session_buf));
- if (ret < 0) {
- goto end_free;
- }
- health_code_update();
-
- ret = 0;
-end_free:
- free(send_session_buf);
- return ret;
-}
-
-/*
- * Send the viewer the list of current streams.
- */
-static
-int viewer_get_new_streams(struct relay_connection *conn)
-{
- int ret, send_streams = 0;
- uint32_t nb_created = 0, nb_unsent = 0, nb_streams = 0, nb_total = 0;
- struct lttng_viewer_new_streams_request request;
- struct lttng_viewer_new_streams_response response;
- struct relay_session *session = NULL;
- uint64_t session_id;
- bool closed = false;
-
- LTTNG_ASSERT(conn);
-
- DBG("Get new streams received");
-
- health_code_update();
-
- /* Receive the request from the connected client. */
- ret = recv_request(conn->sock, &request, sizeof(request));
- if (ret < 0) {
- goto error;
- }
- session_id = be64toh(request.session_id);
-
- health_code_update();
-
- memset(&response, 0, sizeof(response));
-
- session = session_get_by_id(session_id);
- if (!session) {
- DBG("Relay session %" PRIu64 " not found", session_id);
- response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_ERR);
- goto send_reply;
- }
-
- if (!viewer_session_is_attached(conn->viewer_session, session)) {
- response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_ERR);
- goto send_reply;
- }
-
- /*
- * For any new stream, create it with LTTNG_VIEWER_SEEK_BEGINNING since
- * that at this point the client is already attached to the session.Aany
- * initial stream will have been created with the seek type at attach
- * time (for now most readers use the LTTNG_VIEWER_SEEK_LAST on attach).
- * Otherwise any event happening in a new stream between the attach and
- * a call to viewer_get_new_streams will be "lost" (never received) from
- * the viewer's point of view.
- */
- pthread_mutex_lock(&session->lock);
- ret = make_viewer_streams(session,
- conn->viewer_session,
- LTTNG_VIEWER_SEEK_BEGINNING, &nb_total, &nb_unsent,
- &nb_created, &closed);
- if (ret < 0) {
- goto error_unlock_session;
- }
- send_streams = 1;
- response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_OK);
-
- /* Only send back the newly created streams with the unsent ones. */
- nb_streams = nb_created + nb_unsent;
- response.streams_count = htobe32(nb_streams);
-
- /*
- * If the session is closed, HUP when there are no more streams
- * with data.
- */
- if (closed && nb_total == 0) {
- send_streams = 0;
- response.streams_count = 0;
- response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_HUP);
- goto send_reply_unlock;
- }
-send_reply_unlock:
- pthread_mutex_unlock(&session->lock);
-
-send_reply:
- health_code_update();
- ret = send_response(conn->sock, &response, sizeof(response));
- if (ret < 0) {
- goto end_put_session;
- }
- health_code_update();
-
- /*
- * Unknown or empty session, just return gracefully, the viewer
- * knows what is happening.
- */
- if (!send_streams || !nb_streams) {
- ret = 0;
- goto end_put_session;
- }
-
- /*
- * Send stream and *DON'T* ignore the sent flag so every viewer
- * streams that were not sent from that point will be sent to
- * the viewer.
- */
- ret = send_viewer_streams(conn->sock, session_id, 0);
- if (ret < 0) {
- goto end_put_session;
- }
-
-end_put_session:
- if (session) {
- session_put(session);
- }
-error:
- return ret;
-error_unlock_session:
- pthread_mutex_unlock(&session->lock);
- session_put(session);
- return ret;
-}
-
-/*
- * Send the viewer the list of current sessions.
- */
-static
-int viewer_attach_session(struct relay_connection *conn)
-{
- int send_streams = 0;
- ssize_t ret;
- uint32_t nb_streams = 0;
- enum lttng_viewer_seek seek_type;
- struct lttng_viewer_attach_session_request request;
- struct lttng_viewer_attach_session_response response;
- struct relay_session *session = NULL;
- enum lttng_viewer_attach_return_code viewer_attach_status;
- bool closed = false;
- uint64_t session_id;
-
- LTTNG_ASSERT(conn);
-
- health_code_update();
-
- /* Receive the request from the connected client. */
- ret = recv_request(conn->sock, &request, sizeof(request));
- if (ret < 0) {
- goto error;
- }
-
- session_id = be64toh(request.session_id);
- health_code_update();
-
- memset(&response, 0, sizeof(response));
-
- if (!conn->viewer_session) {
- DBG("Client trying to attach before creating a live viewer session");
- response.status = htobe32(LTTNG_VIEWER_ATTACH_NO_SESSION);
- goto send_reply;
- }
-
- session = session_get_by_id(session_id);
- if (!session) {
- DBG("Relay session %" PRIu64 " not found", session_id);
- response.status = htobe32(LTTNG_VIEWER_ATTACH_UNK);
- goto send_reply;
- }
- DBG("Attach session ID %" PRIu64 " received", session_id);
-
- pthread_mutex_lock(&session->lock);
- if (session->live_timer == 0) {
- DBG("Not live session");
- response.status = htobe32(LTTNG_VIEWER_ATTACH_NOT_LIVE);
- goto send_reply;
- }
-
- send_streams = 1;
- viewer_attach_status = viewer_session_attach(conn->viewer_session,
- session);
- if (viewer_attach_status != LTTNG_VIEWER_ATTACH_OK) {
- response.status = htobe32(viewer_attach_status);
- goto send_reply;
- }
-
- switch (be32toh(request.seek)) {
- case LTTNG_VIEWER_SEEK_BEGINNING:
- case LTTNG_VIEWER_SEEK_LAST:
- response.status = htobe32(LTTNG_VIEWER_ATTACH_OK);
- seek_type = be32toh(request.seek);
- break;
- default:
- ERR("Wrong seek parameter");
- response.status = htobe32(LTTNG_VIEWER_ATTACH_SEEK_ERR);
- send_streams = 0;
- goto send_reply;
- }
-
- ret = make_viewer_streams(session,
- conn->viewer_session, seek_type,
- &nb_streams, NULL, NULL, &closed);
- if (ret < 0) {
- goto end_put_session;
- }
- pthread_mutex_unlock(&session->lock);
- session_put(session);
- session = NULL;
-
- response.streams_count = htobe32(nb_streams);
- /*
- * If the session is closed when the viewer is attaching, it
- * means some of the streams may have been concurrently removed,
- * so we don't allow the viewer to attach, even if there are
- * streams available.
- */
- if (closed) {
- send_streams = 0;
- response.streams_count = 0;
- response.status = htobe32(LTTNG_VIEWER_ATTACH_UNK);
- goto send_reply;
- }
-
-send_reply:
- health_code_update();
- ret = send_response(conn->sock, &response, sizeof(response));
- if (ret < 0) {
- goto end_put_session;
- }
- health_code_update();
-
- /*
- * Unknown or empty session, just return gracefully, the viewer
- * knows what is happening.
- */
- if (!send_streams || !nb_streams) {
- ret = 0;
- goto end_put_session;
- }
-
- /* Send stream and ignore the sent flag. */
- ret = send_viewer_streams(conn->sock, session_id, 1);
- if (ret < 0) {
- goto end_put_session;
- }
-
-end_put_session:
- if (session) {
- pthread_mutex_unlock(&session->lock);
- session_put(session);
- }
-error:
- return ret;
-}
-
-/*
- * Open the index file if needed for the given vstream.
- *
- * If an index file is successfully opened, the vstream will set it as its
- * current index file.
- *
- * Return 0 on success, a negative value on error (-ENOENT if not ready yet).
- *
- * Called with rstream lock held.
- */
-static int try_open_index(struct relay_viewer_stream *vstream,
- struct relay_stream *rstream)
-{
- int ret = 0;
- const uint32_t connection_major = rstream->trace->session->major;
- const uint32_t connection_minor = rstream->trace->session->minor;
- enum lttng_trace_chunk_status chunk_status;
-
- if (vstream->index_file) {
- goto end;
- }
-
- /*
- * First time, we open the index file and at least one index is ready.
- */
- if (rstream->index_received_seqcount == 0 ||
- !vstream->stream_file.trace_chunk) {
- ret = -ENOENT;
- goto end;
- }
-
- chunk_status = lttng_index_file_create_from_trace_chunk_read_only(
- vstream->stream_file.trace_chunk, rstream->path_name,
- rstream->channel_name, rstream->tracefile_size,
- vstream->current_tracefile_id,
- lttng_to_index_major(connection_major, connection_minor),
- lttng_to_index_minor(connection_major, connection_minor),
- true, &vstream->index_file);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
- ret = -ENOENT;
- } else {
- ret = -1;
- }
- }
-
-end:
- return ret;
-}
-
-/*
- * Check the status of the index for the given stream. This function
- * updates the index structure if needed and can put (close) the vstream
- * in the HUP situation.
- *
- * Return 0 means that we can proceed with the index. A value of 1 means
- * that the index has been updated and is ready to be sent to the
- * client. A negative value indicates an error that can't be handled.
- *
- * Called with rstream lock held.
- */
-static int check_index_status(struct relay_viewer_stream *vstream,
- struct relay_stream *rstream, struct ctf_trace *trace,
- struct lttng_viewer_index *index)
-{
- int ret;
-
- DBG("Check index status: index_received_seqcount %" PRIu64 " "
- "index_sent_seqcount %" PRIu64 " "
- "for stream %" PRIu64,
- rstream->index_received_seqcount,
- vstream->index_sent_seqcount,
- vstream->stream->stream_handle);
- if ((trace->session->connection_closed || rstream->closed)
- && rstream->index_received_seqcount
- == vstream->index_sent_seqcount) {
- /*
- * Last index sent and session connection or relay
- * stream are closed.
- */
- index->status = htobe32(LTTNG_VIEWER_INDEX_HUP);
- goto hup;
- } else if (rstream->beacon_ts_end != -1ULL &&
- (rstream->index_received_seqcount == 0 ||
- (vstream->index_sent_seqcount != 0 &&
- rstream->index_received_seqcount
- <= vstream->index_sent_seqcount))) {
- /*
- * We've received a synchronization beacon and the last index
- * available has been sent, the index for now is inactive.
- *
- * In this case, we have received a beacon which allows us to
- * inform the client of a time interval during which we can
- * guarantee that there are no events to read (and never will
- * be).
- *
- * The sent seqcount can grow higher than receive seqcount on
- * clear because the rotation performed by clear will push
- * the index_sent_seqcount ahead (see
- * viewer_stream_sync_tracefile_array_tail) and skip over
- * packet sequence numbers.
- */
- index->status = htobe32(LTTNG_VIEWER_INDEX_INACTIVE);
- index->timestamp_end = htobe64(rstream->beacon_ts_end);
- index->stream_id = htobe64(rstream->ctf_stream_id);
- DBG("Check index status: inactive with beacon, for stream %" PRIu64,
- vstream->stream->stream_handle);
- goto index_ready;
- } else if (rstream->index_received_seqcount == 0 ||
- (vstream->index_sent_seqcount != 0 &&
- rstream->index_received_seqcount
- <= vstream->index_sent_seqcount)) {
- /*
- * This checks whether received <= sent seqcount. In
- * this case, we have not received a beacon. Therefore,
- * we can only ask the client to retry later.
- *
- * The sent seqcount can grow higher than receive seqcount on
- * clear because the rotation performed by clear will push
- * the index_sent_seqcount ahead (see
- * viewer_stream_sync_tracefile_array_tail) and skip over
- * packet sequence numbers.
- */
- index->status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
- DBG("Check index status: retry for stream %" PRIu64,
- vstream->stream->stream_handle);
- goto index_ready;
- } else if (!tracefile_array_seq_in_file(rstream->tfa,
- vstream->current_tracefile_id,
- vstream->index_sent_seqcount)) {
- /*
- * The next index we want to send cannot be read either
- * because we need to perform a rotation, or due to
- * the producer having overwritten its trace file.
- */
- DBG("Viewer stream %" PRIu64 " rotation",
- vstream->stream->stream_handle);
- ret = viewer_stream_rotate(vstream);
- if (ret == 1) {
- /* EOF across entire stream. */
- index->status = htobe32(LTTNG_VIEWER_INDEX_HUP);
- goto hup;
- }
- /*
- * If we have been pushed due to overwrite, it
- * necessarily means there is data that can be read in
- * the stream. If we rotated because we reached the end
- * of a tracefile, it means the following tracefile
- * needs to contain at least one index, else we would
- * have already returned LTTNG_VIEWER_INDEX_RETRY to the
- * viewer. The updated index_sent_seqcount needs to
- * point to a readable index entry now.
- *
- * In the case where we "rotate" on a single file, we
- * can end up in a case where the requested index is
- * still unavailable.
- */
- if (rstream->tracefile_count == 1 &&
- !tracefile_array_seq_in_file(
- rstream->tfa,
- vstream->current_tracefile_id,
- vstream->index_sent_seqcount)) {
- index->status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
- DBG("Check index status: retry: "
- "tracefile array sequence number %" PRIu64
- " not in file for stream %" PRIu64,
- vstream->index_sent_seqcount,
- vstream->stream->stream_handle);
- goto index_ready;
- }
- LTTNG_ASSERT(tracefile_array_seq_in_file(rstream->tfa,
- vstream->current_tracefile_id,
- vstream->index_sent_seqcount));
- }
- /* ret == 0 means successful so we continue. */
- ret = 0;
- return ret;
-
-hup:
- viewer_stream_put(vstream);
-index_ready:
- return 1;
-}
-
-static
-void viewer_stream_rotate_to_trace_chunk(struct relay_viewer_stream *vstream,
- struct lttng_trace_chunk *new_trace_chunk)
-{
- lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
-
- if (new_trace_chunk) {
- const bool acquired_reference = lttng_trace_chunk_get(
- new_trace_chunk);
-
- LTTNG_ASSERT(acquired_reference);
- }
-
- vstream->stream_file.trace_chunk = new_trace_chunk;
- viewer_stream_sync_tracefile_array_tail(vstream);
- viewer_stream_close_files(vstream);
-}
-
-/*
- * Send the next index for a stream.
- *
- * Return 0 on success or else a negative value.
- */
-static
-int viewer_get_next_index(struct relay_connection *conn)
-{
- int ret;
- struct lttng_viewer_get_next_index request_index;
- struct lttng_viewer_index viewer_index;
- struct ctf_packet_index packet_index;
- struct relay_viewer_stream *vstream = NULL;
- struct relay_stream *rstream = NULL;
- struct ctf_trace *ctf_trace = NULL;
- struct relay_viewer_stream *metadata_viewer_stream = NULL;
-
- LTTNG_ASSERT(conn);
-
- DBG("Viewer get next index");
-
- memset(&viewer_index, 0, sizeof(viewer_index));
- health_code_update();
-
- ret = recv_request(conn->sock, &request_index, sizeof(request_index));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
-
- vstream = viewer_stream_get_by_id(be64toh(request_index.stream_id));
- if (!vstream) {
- DBG("Client requested index of unknown stream id %" PRIu64,
- (uint64_t) be64toh(request_index.stream_id));
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
- goto send_reply;
- }
-
- /* Use back. ref. Protected by refcounts. */
- rstream = vstream->stream;
- ctf_trace = rstream->trace;
-
- /* metadata_viewer_stream may be NULL. */
- metadata_viewer_stream =
- ctf_trace_get_viewer_metadata_stream(ctf_trace);
-
- pthread_mutex_lock(&rstream->lock);
-
- /*
- * The viewer should not ask for index on metadata stream.
- */
- if (rstream->is_metadata) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
- goto send_reply;
- }
-
- if (rstream->ongoing_rotation.is_set) {
- /* Rotation is ongoing, try again later. */
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
- goto send_reply;
- }
-
- if (rstream->trace->session->ongoing_rotation) {
- /* Rotation is ongoing, try again later. */
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
- goto send_reply;
- }
-
- /*
- * Transition the viewer session into the newest trace chunk available.
- */
- if (!lttng_trace_chunk_ids_equal(
- conn->viewer_session->current_trace_chunk,
- rstream->trace_chunk)) {
- DBG("Relay stream and viewer chunk ids differ");
-
- ret = viewer_session_set_trace_chunk_copy(
- conn->viewer_session,
- rstream->trace_chunk);
- if (ret) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
- goto send_reply;
- }
- }
-
- /*
- * Transition the viewer stream into the latest trace chunk available.
- *
- * Note that the stream must _not_ rotate in one precise condition:
- * the relay stream has rotated to a NULL trace chunk and the viewer
- * stream is consuming the trace chunk that was active just before
- * that rotation to NULL.
- *
- * This allows clients to consume all the packets of a trace chunk
- * after a session's destruction.
- */
- if (conn->viewer_session->current_trace_chunk != vstream->stream_file.trace_chunk &&
- !(rstream->completed_rotation_count == vstream->last_seen_rotation_count + 1 && !rstream->trace_chunk)) {
- DBG("Viewer session and viewer stream chunk differ: "
- "vsession chunk %p vstream chunk %p",
- conn->viewer_session->current_trace_chunk,
- vstream->stream_file.trace_chunk);
- viewer_stream_rotate_to_trace_chunk(vstream,
- conn->viewer_session->current_trace_chunk);
- vstream->last_seen_rotation_count =
- rstream->completed_rotation_count;
- }
-
- ret = check_index_status(vstream, rstream, ctf_trace, &viewer_index);
- if (ret < 0) {
- goto error_put;
- } else if (ret == 1) {
- /*
- * We have no index to send and check_index_status has populated
- * viewer_index's status.
- */
- goto send_reply;
- }
- /* At this point, ret is 0 thus we will be able to read the index. */
- LTTNG_ASSERT(!ret);
-
- /* Try to open an index if one is needed for that stream. */
- ret = try_open_index(vstream, rstream);
- if (ret == -ENOENT) {
- if (rstream->closed) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
- goto send_reply;
- } else {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
- goto send_reply;
- }
- }
- if (ret < 0) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
- goto send_reply;
- }
-
- /*
- * vstream->stream_fd may be NULL if it has been closed by
- * tracefile rotation, or if we are at the beginning of the
- * stream. We open the data stream file here to protect against
- * overwrite caused by tracefile rotation (in association with
- * unlink performed before overwrite).
- */
- if (!vstream->stream_file.handle) {
- char file_path[LTTNG_PATH_MAX];
- enum lttng_trace_chunk_status status;
- struct fs_handle *fs_handle;
-
- ret = utils_stream_file_path(rstream->path_name,
- rstream->channel_name, rstream->tracefile_size,
- vstream->current_tracefile_id, NULL, file_path,
- sizeof(file_path));
- if (ret < 0) {
- goto error_put;
- }
-
- /*
- * It is possible the the file we are trying to open is
- * missing if the stream has been closed (application exits with
- * per-pid buffers) and a clear command has been performed.
- */
- status = lttng_trace_chunk_open_fs_handle(
- vstream->stream_file.trace_chunk,
- file_path, O_RDONLY, 0, &fs_handle, true);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- if (status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE &&
- rstream->closed) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
- goto send_reply;
- }
- PERROR("Failed to open trace file for viewer stream");
- goto error_put;
- }
- vstream->stream_file.handle = fs_handle;
- }
-
- ret = check_new_streams(conn);
- if (ret < 0) {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
- goto send_reply;
- } else if (ret == 1) {
- viewer_index.flags |= LTTNG_VIEWER_FLAG_NEW_STREAM;
- }
-
- ret = lttng_index_file_read(vstream->index_file, &packet_index);
- if (ret) {
- ERR("Relay error reading index file");
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
- goto send_reply;
- } else {
- viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_OK);
- vstream->index_sent_seqcount++;
- }
-
- /*
- * Indexes are stored in big endian, no need to switch before sending.
- */
- DBG("Sending viewer index for stream %" PRIu64 " offset %" PRIu64,
- rstream->stream_handle,
- (uint64_t) be64toh(packet_index.offset));
- viewer_index.offset = packet_index.offset;
- viewer_index.packet_size = packet_index.packet_size;
- viewer_index.content_size = packet_index.content_size;
- viewer_index.timestamp_begin = packet_index.timestamp_begin;
- viewer_index.timestamp_end = packet_index.timestamp_end;
- viewer_index.events_discarded = packet_index.events_discarded;
- viewer_index.stream_id = packet_index.stream_id;
-
-send_reply:
- if (rstream) {
- pthread_mutex_unlock(&rstream->lock);
- }
-
- if (metadata_viewer_stream) {
- pthread_mutex_lock(&metadata_viewer_stream->stream->lock);
- DBG("get next index metadata check: recv %" PRIu64
- " sent %" PRIu64,
- metadata_viewer_stream->stream->metadata_received,
- metadata_viewer_stream->metadata_sent);
- if (!metadata_viewer_stream->stream->metadata_received ||
- metadata_viewer_stream->stream->metadata_received >
- metadata_viewer_stream->metadata_sent) {
- viewer_index.flags |= LTTNG_VIEWER_FLAG_NEW_METADATA;
- }
- pthread_mutex_unlock(&metadata_viewer_stream->stream->lock);
- }
-
- viewer_index.flags = htobe32(viewer_index.flags);
- health_code_update();
-
- ret = send_response(conn->sock, &viewer_index, sizeof(viewer_index));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
-
- if (vstream) {
- DBG("Index %" PRIu64 " for stream %" PRIu64 " sent",
- vstream->index_sent_seqcount,
- vstream->stream->stream_handle);
- }
-end:
- if (metadata_viewer_stream) {
- viewer_stream_put(metadata_viewer_stream);
- }
- if (vstream) {
- viewer_stream_put(vstream);
- }
- return ret;
-
-error_put:
- pthread_mutex_unlock(&rstream->lock);
- if (metadata_viewer_stream) {
- viewer_stream_put(metadata_viewer_stream);
- }
- viewer_stream_put(vstream);
- return ret;
-}
-
-/*
- * Send the next index for a stream
- *
- * Return 0 on success or else a negative value.
- */
-static
-int viewer_get_packet(struct relay_connection *conn)
-{
- int ret;
- off_t lseek_ret;
- char *reply = NULL;
- struct lttng_viewer_get_packet get_packet_info;
- struct lttng_viewer_trace_packet reply_header;
- struct relay_viewer_stream *vstream = NULL;
- uint32_t reply_size = sizeof(reply_header);
- uint32_t packet_data_len = 0;
- ssize_t read_len;
- uint64_t stream_id;
-
- DBG2("Relay get data packet");
-
- health_code_update();
-
- ret = recv_request(conn->sock, &get_packet_info,
- sizeof(get_packet_info));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
-
- /* From this point on, the error label can be reached. */
- memset(&reply_header, 0, sizeof(reply_header));
- stream_id = (uint64_t) be64toh(get_packet_info.stream_id);
-
- vstream = viewer_stream_get_by_id(stream_id);
- if (!vstream) {
- DBG("Client requested packet of unknown stream id %" PRIu64,
- stream_id);
- reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_ERR);
- goto send_reply_nolock;
- } else {
- packet_data_len = be32toh(get_packet_info.len);
- reply_size += packet_data_len;
- }
-
- reply = zmalloc(reply_size);
- if (!reply) {
- PERROR("packet reply zmalloc");
- reply_size = sizeof(reply_header);
- goto error;
- }
-
- pthread_mutex_lock(&vstream->stream->lock);
- lseek_ret = fs_handle_seek(vstream->stream_file.handle,
- be64toh(get_packet_info.offset), SEEK_SET);
- if (lseek_ret < 0) {
- PERROR("Failed to seek file system handle of viewer stream %" PRIu64
- " to offset %" PRIu64,
- stream_id,
- (uint64_t) be64toh(get_packet_info.offset));
- goto error;
- }
- read_len = fs_handle_read(vstream->stream_file.handle,
- reply + sizeof(reply_header), packet_data_len);
- if (read_len < packet_data_len) {
- PERROR("Failed to read from file system handle of viewer stream id %" PRIu64
- ", offset: %" PRIu64,
- stream_id,
- (uint64_t) be64toh(get_packet_info.offset));
- goto error;
- }
- reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_OK);
- reply_header.len = htobe32(packet_data_len);
- goto send_reply;
-
-error:
- /* No payload to send on error. */
- reply_size = sizeof(reply_header);
- reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_ERR);
-
-send_reply:
- if (vstream) {
- pthread_mutex_unlock(&vstream->stream->lock);
- }
-send_reply_nolock:
-
- health_code_update();
-
- if (reply) {
- memcpy(reply, &reply_header, sizeof(reply_header));
- ret = send_response(conn->sock, reply, reply_size);
- } else {
- /* No reply to send. */
- ret = send_response(conn->sock, &reply_header,
- reply_size);
- }
-
- health_code_update();
- if (ret < 0) {
- PERROR("sendmsg of packet data failed");
- goto end_free;
- }
-
- DBG("Sent %u bytes for stream %" PRIu64, reply_size, stream_id);
-
-end_free:
- free(reply);
-end:
- if (vstream) {
- viewer_stream_put(vstream);
- }
- return ret;
-}
-
-/*
- * Send the session's metadata
- *
- * Return 0 on success else a negative value.
- */
-static
-int viewer_get_metadata(struct relay_connection *conn)
-{
- int ret = 0;
- int fd = -1;
- ssize_t read_len;
- uint64_t len = 0;
- char *data = NULL;
- struct lttng_viewer_get_metadata request;
- struct lttng_viewer_metadata_packet reply;
- struct relay_viewer_stream *vstream = NULL;
-
- LTTNG_ASSERT(conn);
-
- DBG("Relay get metadata");
-
- health_code_update();
-
- ret = recv_request(conn->sock, &request, sizeof(request));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
-
- memset(&reply, 0, sizeof(reply));
-
- vstream = viewer_stream_get_by_id(be64toh(request.stream_id));
- if (!vstream) {
- /*
- * The metadata stream can be closed by a CLOSE command
- * just before we attach. It can also be closed by
- * per-pid tracing during tracing. Therefore, it is
- * possible that we cannot find this viewer stream.
- * Reply back to the client with an error if we cannot
- * find it.
- */
- DBG("Client requested metadata of unknown stream id %" PRIu64,
- (uint64_t) be64toh(request.stream_id));
- reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
- goto send_reply;
- }
- pthread_mutex_lock(&vstream->stream->lock);
- if (!vstream->stream->is_metadata) {
- ERR("Invalid metadata stream");
- goto error;
- }
-
- if (vstream->metadata_sent >= vstream->stream->metadata_received) {
- /*
- * The live viewers expect to receive a NO_NEW_METADATA
- * status before a stream disappears, otherwise they abort the
- * entire live connection when receiving an error status.
- *
- * Clear feature resets the metadata_sent to 0 until the
- * same metadata is received again.
- */
- reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
- /*
- * The live viewer considers a closed 0 byte metadata stream as
- * an error.
- */
- if (vstream->metadata_sent > 0) {
- vstream->stream->no_new_metadata_notified = true;
- if (vstream->stream->closed) {
- /* Release ownership for the viewer metadata stream. */
- viewer_stream_put(vstream);
- }
- }
- goto send_reply;
- }
-
- if (vstream->stream->trace_chunk &&
- !lttng_trace_chunk_ids_equal(
- conn->viewer_session->current_trace_chunk,
- vstream->stream->trace_chunk)) {
- /* A rotation has occurred on the relay stream. */
- DBG("Metadata relay stream and viewer chunk ids differ");
-
- ret = viewer_session_set_trace_chunk_copy(
- conn->viewer_session,
- vstream->stream->trace_chunk);
- if (ret) {
- reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
- goto send_reply;
- }
- }
-
- if (conn->viewer_session->current_trace_chunk &&
- conn->viewer_session->current_trace_chunk !=
- vstream->stream_file.trace_chunk) {
- bool acquired_reference;
-
- DBG("Viewer session and viewer stream chunk differ: "
- "vsession chunk %p vstream chunk %p",
- conn->viewer_session->current_trace_chunk,
- vstream->stream_file.trace_chunk);
- lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
- acquired_reference = lttng_trace_chunk_get(conn->viewer_session->current_trace_chunk);
- LTTNG_ASSERT(acquired_reference);
- vstream->stream_file.trace_chunk =
- conn->viewer_session->current_trace_chunk;
- viewer_stream_close_files(vstream);
- }
-
- len = vstream->stream->metadata_received - vstream->metadata_sent;
-
- if (!vstream->stream_file.trace_chunk) {
- reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
- len = 0;
- goto send_reply;
- } else if (vstream->stream_file.trace_chunk &&
- !vstream->stream_file.handle && len > 0) {
- /*
- * Either this is the first time the metadata file is read, or a
- * rotation of the corresponding relay stream has occurred.
- */
- struct fs_handle *fs_handle;
- char file_path[LTTNG_PATH_MAX];
- enum lttng_trace_chunk_status status;
- struct relay_stream *rstream = vstream->stream;
-
- ret = utils_stream_file_path(rstream->path_name,
- rstream->channel_name, rstream->tracefile_size,
- vstream->current_tracefile_id, NULL, file_path,
- sizeof(file_path));
- if (ret < 0) {
- goto error;
- }
-
- /*
- * It is possible the the metadata file we are trying to open is
- * missing if the stream has been closed (application exits with
- * per-pid buffers) and a clear command has been performed.
- */
- status = lttng_trace_chunk_open_fs_handle(
- vstream->stream_file.trace_chunk,
- file_path, O_RDONLY, 0, &fs_handle, true);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- if (status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
- reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
- len = 0;
- if (vstream->stream->closed) {
- viewer_stream_put(vstream);
- }
- goto send_reply;
- }
- PERROR("Failed to open metadata file for viewer stream");
- goto error;
- }
- vstream->stream_file.handle = fs_handle;
-
- if (vstream->metadata_sent != 0) {
- /*
- * The client does not expect to receive any metadata
- * it has received and metadata files in successive
- * chunks must be a strict superset of one another.
- *
- * Skip the first `metadata_sent` bytes to ensure
- * they are not sent a second time to the client.
- *
- * Baring a block layer error or an internal error,
- * this seek should not fail as
- * `vstream->stream->metadata_received` is reset when
- * a relay stream is rotated. If this is reached, it is
- * safe to assume that
- * `metadata_received` > `metadata_sent`.
- */
- const off_t seek_ret = fs_handle_seek(fs_handle,
- vstream->metadata_sent, SEEK_SET);
-
- if (seek_ret < 0) {
- PERROR("Failed to seek metadata viewer stream file to `sent` position: pos = %" PRId64,
- vstream->metadata_sent);
- reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
- goto send_reply;
- }
- }
- }
-
- reply.len = htobe64(len);
- data = zmalloc(len);
- if (!data) {
- PERROR("viewer metadata zmalloc");
- goto error;
- }
-
- fd = fs_handle_get_fd(vstream->stream_file.handle);
- if (fd < 0) {
- ERR("Failed to restore viewer stream file system handle");
- goto error;
- }
- read_len = lttng_read(fd, data, len);
- fs_handle_put_fd(vstream->stream_file.handle);
- fd = -1;
- if (read_len < len) {
- if (read_len < 0) {
- PERROR("Failed to read metadata file");
- goto error;
- } else {
- /*
- * A clear has been performed which prevents the relay
- * from sending `len` bytes of metadata.
- *
- * It is important not to send any metadata if we
- * couldn't read all the available metadata in one shot:
- * sending partial metadata can cause the client to
- * attempt to parse an incomplete (incoherent) metadata
- * stream, which would result in an error.
- */
- const off_t seek_ret = fs_handle_seek(
- vstream->stream_file.handle, -read_len,
- SEEK_CUR);
-
- DBG("Failed to read metadata: requested = %" PRIu64 ", got = %zd",
- len, read_len);
- read_len = 0;
- len = 0;
- if (seek_ret < 0) {
- PERROR("Failed to restore metadata file position after partial read");
- ret = -1;
- goto error;
- }
- }
- }
- vstream->metadata_sent += read_len;
- reply.status = htobe32(LTTNG_VIEWER_METADATA_OK);
-
- goto send_reply;
-
-error:
- reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
-
-send_reply:
- health_code_update();
- if (vstream) {
- pthread_mutex_unlock(&vstream->stream->lock);
- }
- ret = send_response(conn->sock, &reply, sizeof(reply));
- if (ret < 0) {
- goto end_free;
- }
- health_code_update();
-
- if (len > 0) {
- ret = send_response(conn->sock, data, len);
- if (ret < 0) {
- goto end_free;
- }
- }
-
- DBG("Sent %" PRIu64 " bytes of metadata for stream %" PRIu64, len,
- (uint64_t) be64toh(request.stream_id));
-
- DBG("Metadata sent");
-
-end_free:
- free(data);
-end:
- if (vstream) {
- viewer_stream_put(vstream);
- }
- return ret;
-}
-
-/*
- * Create a viewer session.
- *
- * Return 0 on success or else a negative value.
- */
-static
-int viewer_create_session(struct relay_connection *conn)
-{
- int ret;
- struct lttng_viewer_create_session_response resp;
-
- DBG("Viewer create session received");
-
- memset(&resp, 0, sizeof(resp));
- resp.status = htobe32(LTTNG_VIEWER_CREATE_SESSION_OK);
- conn->viewer_session = viewer_session_create();
- if (!conn->viewer_session) {
- ERR("Allocation viewer session");
- resp.status = htobe32(LTTNG_VIEWER_CREATE_SESSION_ERR);
- goto send_reply;
- }
-
-send_reply:
- health_code_update();
- ret = send_response(conn->sock, &resp, sizeof(resp));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
- ret = 0;
-
-end:
- return ret;
-}
-
-/*
- * Detach a viewer session.
- *
- * Return 0 on success or else a negative value.
- */
-static
-int viewer_detach_session(struct relay_connection *conn)
-{
- int ret;
- struct lttng_viewer_detach_session_response response;
- struct lttng_viewer_detach_session_request request;
- struct relay_session *session = NULL;
- uint64_t viewer_session_to_close;
-
- DBG("Viewer detach session received");
-
- LTTNG_ASSERT(conn);
-
- health_code_update();
-
- /* Receive the request from the connected client. */
- ret = recv_request(conn->sock, &request, sizeof(request));
- if (ret < 0) {
- goto end;
- }
- viewer_session_to_close = be64toh(request.session_id);
-
- if (!conn->viewer_session) {
- DBG("Client trying to detach before creating a live viewer session");
- response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_ERR);
- goto send_reply;
- }
-
- health_code_update();
-
- memset(&response, 0, sizeof(response));
- DBG("Detaching from session ID %" PRIu64, viewer_session_to_close);
-
- session = session_get_by_id(be64toh(request.session_id));
- if (!session) {
- DBG("Relay session %" PRIu64 " not found",
- (uint64_t) be64toh(request.session_id));
- response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_UNK);
- goto send_reply;
- }
-
- ret = viewer_session_is_attached(conn->viewer_session, session);
- if (ret != 1) {
- DBG("Not attached to this session");
- response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_ERR);
- goto send_reply_put;
- }
-
- viewer_session_close_one_session(conn->viewer_session, session);
- response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_OK);
- DBG("Session %" PRIu64 " detached.", viewer_session_to_close);
-
-send_reply_put:
- session_put(session);
-
-send_reply:
- health_code_update();
- ret = send_response(conn->sock, &response, sizeof(response));
- if (ret < 0) {
- goto end;
- }
- health_code_update();
- ret = 0;
-
-end:
- return ret;
-}
-
-/*
- * live_relay_unknown_command: send -1 if received unknown command
- */
-static
-void live_relay_unknown_command(struct relay_connection *conn)
-{
- struct lttcomm_relayd_generic_reply reply;
-
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- (void) send_response(conn->sock, &reply, sizeof(reply));
-}
-
-/*
- * Process the commands received on the control socket
- */
-static
-int process_control(struct lttng_viewer_cmd *recv_hdr,
- struct relay_connection *conn)
-{
- int ret = 0;
- uint32_t msg_value;
-
- msg_value = be32toh(recv_hdr->cmd);
-
- /*
- * Make sure we've done the version check before any command other then a
- * new client connection.
- */
- if (msg_value != LTTNG_VIEWER_CONNECT && !conn->version_check_done) {
- ERR("Viewer conn value %" PRIu32 " before version check", msg_value);
- ret = -1;
- goto end;
- }
-
- switch (msg_value) {
- case LTTNG_VIEWER_CONNECT:
- ret = viewer_connect(conn);
- break;
- case LTTNG_VIEWER_LIST_SESSIONS:
- ret = viewer_list_sessions(conn);
- break;
- case LTTNG_VIEWER_ATTACH_SESSION:
- ret = viewer_attach_session(conn);
- break;
- case LTTNG_VIEWER_GET_NEXT_INDEX:
- ret = viewer_get_next_index(conn);
- break;
- case LTTNG_VIEWER_GET_PACKET:
- ret = viewer_get_packet(conn);
- break;
- case LTTNG_VIEWER_GET_METADATA:
- ret = viewer_get_metadata(conn);
- break;
- case LTTNG_VIEWER_GET_NEW_STREAMS:
- ret = viewer_get_new_streams(conn);
- break;
- case LTTNG_VIEWER_CREATE_SESSION:
- ret = viewer_create_session(conn);
- break;
- case LTTNG_VIEWER_DETACH_SESSION:
- ret = viewer_detach_session(conn);
- break;
- default:
- ERR("Received unknown viewer command (%u)",
- be32toh(recv_hdr->cmd));
- live_relay_unknown_command(conn);
- ret = -1;
- goto end;
- }
-
-end:
- return ret;
-}
-
-static
-void cleanup_connection_pollfd(struct lttng_poll_event *events, int pollfd)
-{
- int ret;
-
- (void) lttng_poll_del(events, pollfd);
-
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &pollfd, 1,
- fd_tracker_util_close_fd, NULL);
- if (ret < 0) {
- ERR("Closing pollfd %d", pollfd);
- }
-}
-
-/*
- * This thread does the actual work
- */
-static
-void *thread_worker(void *data)
-{
- int ret, err = -1;
- uint32_t nb_fd;
- struct lttng_poll_event events;
- struct lttng_ht *viewer_connections_ht;
- struct lttng_ht_iter iter;
- struct lttng_viewer_cmd recv_hdr;
- struct relay_connection *destroy_conn;
-
- DBG("[thread] Live viewer relay worker started");
-
- rcu_register_thread();
-
- health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_WORKER);
-
- if (testpoint(relayd_thread_live_worker)) {
- goto error_testpoint;
- }
-
- /* table of connections indexed on socket */
- viewer_connections_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG);
- if (!viewer_connections_ht) {
- goto viewer_connections_ht_error;
- }
-
- ret = create_named_thread_poll_set(&events, 2,
- "Live viewer worker thread epoll");
- if (ret < 0) {
- goto error_poll_create;
- }
-
- ret = lttng_poll_add(&events, live_conn_pipe[0], LPOLLIN | LPOLLRDHUP);
- if (ret < 0) {
- goto error;
- }
-
-restart:
- while (1) {
- int i;
-
- health_code_update();
-
- /* Infinite blocking call, waiting for transmission */
- DBG3("Relayd live viewer worker thread polling...");
- health_poll_entry();
- ret = lttng_poll_wait(&events, -1);
- health_poll_exit();
- if (ret < 0) {
- /*
- * Restart interrupted system call.
- */
- if (errno == EINTR) {
- goto restart;
- }
- goto error;
- }
-
- nb_fd = ret;
-
- /*
- * Process control. The control connection is prioritised so we don't
- * starve it with high throughput tracing data on the data
- * connection.
- */
- for (i = 0; i < nb_fd; i++) {
- /* Fetch once the poll data */
- uint32_t revents = LTTNG_POLL_GETEV(&events, i);
- int pollfd = LTTNG_POLL_GETFD(&events, i);
-
- health_code_update();
-
- /* Thread quit pipe has been closed. Killing thread. */
- ret = check_thread_quit_pipe(pollfd, revents);
- if (ret) {
- err = 0;
- goto exit;
- }
-
- /* Inspect the relay conn pipe for new connection. */
- if (pollfd == live_conn_pipe[0]) {
- if (revents & LPOLLIN) {
- struct relay_connection *conn;
-
- ret = lttng_read(live_conn_pipe[0],
- &conn, sizeof(conn));
- if (ret < 0) {
- goto error;
- }
- ret = lttng_poll_add(&events,
- conn->sock->fd,
- LPOLLIN | LPOLLRDHUP);
- if (ret) {
- ERR("Failed to add new live connection file descriptor to poll set");
- goto error;
- }
- connection_ht_add(viewer_connections_ht, conn);
- DBG("Connection socket %d added to poll", conn->sock->fd);
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- ERR("Relay live pipe error");
- goto error;
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- goto error;
- }
- } else {
- /* Connection activity. */
- struct relay_connection *conn;
-
- conn = connection_get_by_sock(viewer_connections_ht, pollfd);
- if (!conn) {
- continue;
- }
-
- if (revents & LPOLLIN) {
- ret = conn->sock->ops->recvmsg(conn->sock, &recv_hdr,
- sizeof(recv_hdr), 0);
- if (ret <= 0) {
- /* Connection closed. */
- cleanup_connection_pollfd(&events, pollfd);
- /* Put "create" ownership reference. */
- connection_put(conn);
- DBG("Viewer control conn closed with %d", pollfd);
- } else {
- ret = process_control(&recv_hdr, conn);
- if (ret < 0) {
- /* Clear the session on error. */
- cleanup_connection_pollfd(&events, pollfd);
- /* Put "create" ownership reference. */
- connection_put(conn);
- DBG("Viewer connection closed with %d", pollfd);
- }
- }
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- cleanup_connection_pollfd(&events, pollfd);
- /* Put "create" ownership reference. */
- connection_put(conn);
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- connection_put(conn);
- goto error;
- }
- /* Put local "get_by_sock" reference. */
- connection_put(conn);
- }
- }
- }
-
-exit:
-error:
- (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
-
- /* Cleanup remaining connection object. */
- rcu_read_lock();
- cds_lfht_for_each_entry(viewer_connections_ht->ht, &iter.iter,
- destroy_conn,
- sock_n.node) {
- health_code_update();
- connection_put(destroy_conn);
- }
- rcu_read_unlock();
-error_poll_create:
- lttng_ht_destroy(viewer_connections_ht);
-viewer_connections_ht_error:
- /* Close relay conn pipes */
- (void) fd_tracker_util_pipe_close(the_fd_tracker, live_conn_pipe);
- if (err) {
- DBG("Viewer worker thread exited with error");
- }
- DBG("Viewer worker thread cleanup complete");
-error_testpoint:
- if (err) {
- health_error();
- ERR("Health error occurred in %s", __func__);
- }
- health_unregister(health_relayd);
- if (lttng_relay_stop_threads()) {
- ERR("Error stopping threads");
- }
- rcu_unregister_thread();
- return NULL;
-}
-
-/*
- * Create the relay command pipe to wake thread_manage_apps.
- * Closed in cleanup().
- */
-static int create_conn_pipe(void)
-{
- return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
- "Live connection pipe", live_conn_pipe);
-}
-
-int relayd_live_join(void)
-{
- int ret, retval = 0;
- void *status;
-
- ret = pthread_join(live_listener_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join live listener");
- retval = -1;
- }
-
- ret = pthread_join(live_worker_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join live worker");
- retval = -1;
- }
-
- ret = pthread_join(live_dispatcher_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join live dispatcher");
- retval = -1;
- }
-
- cleanup_relayd_live();
-
- return retval;
-}
-
-/*
- * main
- */
-int relayd_live_create(struct lttng_uri *uri)
-{
- int ret = 0, retval = 0;
- void *status;
- int is_root;
-
- if (!uri) {
- retval = -1;
- goto exit_init_data;
- }
- live_uri = uri;
-
- /* Check if daemon is UID = 0 */
- is_root = !getuid();
-
- if (!is_root) {
- if (live_uri->port < 1024) {
- ERR("Need to be root to use ports < 1024");
- retval = -1;
- goto exit_init_data;
- }
- }
-
- /* Setup the thread apps communication pipe. */
- if (create_conn_pipe()) {
- retval = -1;
- goto exit_init_data;
- }
-
- /* Init relay command queue. */
- cds_wfcq_init(&viewer_conn_queue.head, &viewer_conn_queue.tail);
-
- /* Set up max poll set size */
- if (lttng_poll_set_max_size()) {
- retval = -1;
- goto exit_init_data;
- }
-
- /* Setup the dispatcher thread */
- ret = pthread_create(&live_dispatcher_thread, default_pthread_attr(),
- thread_dispatcher, (void *) NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create viewer dispatcher");
- retval = -1;
- goto exit_dispatcher_thread;
- }
-
- /* Setup the worker thread */
- ret = pthread_create(&live_worker_thread, default_pthread_attr(),
- thread_worker, NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create viewer worker");
- retval = -1;
- goto exit_worker_thread;
- }
-
- /* Setup the listener thread */
- ret = pthread_create(&live_listener_thread, default_pthread_attr(),
- thread_listener, (void *) NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create viewer listener");
- retval = -1;
- goto exit_listener_thread;
- }
-
- /*
- * All OK, started all threads.
- */
- return retval;
-
- /*
- * Join on the live_listener_thread should anything be added after
- * the live_listener thread's creation.
- */
-
-exit_listener_thread:
-
- ret = pthread_join(live_worker_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join live worker");
- retval = -1;
- }
-exit_worker_thread:
-
- ret = pthread_join(live_dispatcher_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join live dispatcher");
- retval = -1;
- }
-exit_dispatcher_thread:
-
-exit_init_data:
- cleanup_relayd_live();
-
- return retval;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <urcu/futex.h>
+#include <urcu/rculist.h>
+#include <urcu/uatomic.h>
+
+#include <common/common.h>
+#include <common/compat/endian.h>
+#include <common/compat/poll.h>
+#include <common/compat/socket.h>
+#include <common/defaults.h>
+#include <common/fd-tracker/utils.h>
+#include <common/fs-handle.h>
+#include <common/futex.h>
+#include <common/index/index.h>
+#include <common/sessiond-comm/inet.h>
+#include <common/sessiond-comm/relayd.h>
+#include <common/sessiond-comm/sessiond-comm.h>
+#include <common/uri.h>
+#include <common/utils.h>
+#include <lttng/lttng.h>
+
+#include "cmd.h"
+#include "connection.h"
+#include "ctf-trace.h"
+#include "health-relayd.h"
+#include "live.h"
+#include "lttng-relayd.h"
+#include "session.h"
+#include "stream.h"
+#include "testpoint.h"
+#include "utils.h"
+#include "viewer-session.h"
+#include "viewer-stream.h"
+
+#define SESSION_BUF_DEFAULT_COUNT 16
+
+static struct lttng_uri *live_uri;
+
+/*
+ * This pipe is used to inform the worker thread that a command is queued and
+ * ready to be processed.
+ */
+static int live_conn_pipe[2] = { -1, -1 };
+
+/* Shared between threads */
+static int live_dispatch_thread_exit;
+
+static pthread_t live_listener_thread;
+static pthread_t live_dispatcher_thread;
+static pthread_t live_worker_thread;
+
+/*
+ * Relay command queue.
+ *
+ * The live_thread_listener and live_thread_dispatcher communicate with this
+ * queue.
+ */
+static struct relay_conn_queue viewer_conn_queue;
+
+static uint64_t last_relay_viewer_session_id;
+static pthread_mutex_t last_relay_viewer_session_id_lock =
+ PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Cleanup the daemon
+ */
+static
+void cleanup_relayd_live(void)
+{
+ DBG("Cleaning up");
+
+ free(live_uri);
+}
+
+/*
+ * Receive a request buffer using a given socket, destination allocated buffer
+ * of length size.
+ *
+ * Return the size of the received message or else a negative value on error
+ * with errno being set by recvmsg() syscall.
+ */
+static
+ssize_t recv_request(struct lttcomm_sock *sock, void *buf, size_t size)
+{
+ ssize_t ret;
+
+ ret = sock->ops->recvmsg(sock, buf, size, 0);
+ if (ret < 0 || ret != size) {
+ if (ret == 0) {
+ /* Orderly shutdown. Not necessary to print an error. */
+ DBG("Socket %d did an orderly shutdown", sock->fd);
+ } else {
+ ERR("Relay failed to receive request.");
+ }
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/*
+ * Send a response buffer using a given socket, source allocated buffer of
+ * length size.
+ *
+ * Return the size of the sent message or else a negative value on error with
+ * errno being set by sendmsg() syscall.
+ */
+static
+ssize_t send_response(struct lttcomm_sock *sock, void *buf, size_t size)
+{
+ ssize_t ret;
+
+ ret = sock->ops->sendmsg(sock, buf, size, 0);
+ if (ret < 0) {
+ ERR("Relayd failed to send response.");
+ }
+
+ return ret;
+}
+
+/*
+ * Atomically check if new streams got added in one of the sessions attached
+ * and reset the flag to 0.
+ *
+ * Returns 1 if new streams got added, 0 if nothing changed, a negative value
+ * on error.
+ */
+static
+int check_new_streams(struct relay_connection *conn)
+{
+ struct relay_session *session;
+ unsigned long current_val;
+ int ret = 0;
+
+ if (!conn->viewer_session) {
+ goto end;
+ }
+ rcu_read_lock();
+ cds_list_for_each_entry_rcu(session,
+ &conn->viewer_session->session_list,
+ viewer_session_node) {
+ if (!session_get(session)) {
+ continue;
+ }
+ current_val = uatomic_cmpxchg(&session->new_streams, 1, 0);
+ ret = current_val;
+ session_put(session);
+ if (ret == 1) {
+ goto end;
+ }
+ }
+end:
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Send viewer streams to the given socket. The ignore_sent_flag indicates if
+ * this function should ignore the sent flag or not.
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+ssize_t send_viewer_streams(struct lttcomm_sock *sock,
+ uint64_t session_id, unsigned int ignore_sent_flag)
+{
+ ssize_t ret;
+ struct lttng_ht_iter iter;
+ struct relay_viewer_stream *vstream;
+
+ rcu_read_lock();
+
+ cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter, vstream,
+ stream_n.node) {
+ struct ctf_trace *ctf_trace;
+ struct lttng_viewer_stream send_stream = {};
+
+ health_code_update();
+
+ if (!viewer_stream_get(vstream)) {
+ continue;
+ }
+
+ pthread_mutex_lock(&vstream->stream->lock);
+ /* Ignore if not the same session. */
+ if (vstream->stream->trace->session->id != session_id ||
+ (!ignore_sent_flag && vstream->sent_flag)) {
+ pthread_mutex_unlock(&vstream->stream->lock);
+ viewer_stream_put(vstream);
+ continue;
+ }
+
+ ctf_trace = vstream->stream->trace;
+ send_stream.id = htobe64(vstream->stream->stream_handle);
+ send_stream.ctf_trace_id = htobe64(ctf_trace->id);
+ send_stream.metadata_flag = htobe32(
+ vstream->stream->is_metadata);
+ if (lttng_strncpy(send_stream.path_name, vstream->path_name,
+ sizeof(send_stream.path_name))) {
+ pthread_mutex_unlock(&vstream->stream->lock);
+ viewer_stream_put(vstream);
+ ret = -1; /* Error. */
+ goto end_unlock;
+ }
+ if (lttng_strncpy(send_stream.channel_name,
+ vstream->channel_name,
+ sizeof(send_stream.channel_name))) {
+ pthread_mutex_unlock(&vstream->stream->lock);
+ viewer_stream_put(vstream);
+ ret = -1; /* Error. */
+ goto end_unlock;
+ }
+
+ DBG("Sending stream %" PRIu64 " to viewer",
+ vstream->stream->stream_handle);
+ vstream->sent_flag = 1;
+ pthread_mutex_unlock(&vstream->stream->lock);
+
+ ret = send_response(sock, &send_stream, sizeof(send_stream));
+ viewer_stream_put(vstream);
+ if (ret < 0) {
+ goto end_unlock;
+ }
+ }
+
+ ret = 0;
+
+end_unlock:
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Create every viewer stream possible for the given session with the seek
+ * type. Three counters *can* be return which are in order the total amount of
+ * viewer stream of the session, the number of unsent stream and the number of
+ * stream created. Those counters can be NULL and thus will be ignored.
+ *
+ * session must be locked to ensure that we see either none or all initial
+ * streams for a session, but no intermediate state..
+ *
+ * Return 0 on success or else a negative value.
+ */
+static int make_viewer_streams(struct relay_session *relay_session,
+ struct relay_viewer_session *viewer_session,
+ enum lttng_viewer_seek seek_t,
+ uint32_t *nb_total,
+ uint32_t *nb_unsent,
+ uint32_t *nb_created,
+ bool *closed)
+{
+ int ret;
+ struct lttng_ht_iter iter;
+ struct ctf_trace *ctf_trace;
+ struct relay_stream *relay_stream = NULL;
+
+ LTTNG_ASSERT(relay_session);
+ ASSERT_LOCKED(relay_session->lock);
+
+ if (relay_session->connection_closed) {
+ *closed = true;
+ }
+
+ /*
+ * Create viewer streams for relay streams that are ready to be
+ * used for a the given session id only.
+ */
+ rcu_read_lock();
+ cds_lfht_for_each_entry (relay_session->ctf_traces_ht->ht, &iter.iter,
+ ctf_trace, node.node) {
+ bool trace_has_metadata_stream = false;
+
+ health_code_update();
+
+ if (!ctf_trace_get(ctf_trace)) {
+ continue;
+ }
+
+ /*
+ * Iterate over all the streams of the trace to see if we have a
+ * metadata stream.
+ */
+ cds_list_for_each_entry_rcu(relay_stream,
+ &ctf_trace->stream_list, stream_node)
+ {
+ bool is_metadata_stream;
+
+ pthread_mutex_lock(&relay_stream->lock);
+ is_metadata_stream = relay_stream->is_metadata;
+ pthread_mutex_unlock(&relay_stream->lock);
+
+ if (is_metadata_stream) {
+ trace_has_metadata_stream = true;
+ break;
+ }
+ }
+
+ relay_stream = NULL;
+
+ /*
+ * If there is no metadata stream in this trace at the moment
+ * and we never sent one to the viewer, skip the trace. We
+ * accept that the viewer will not see this trace at all.
+ */
+ if (!trace_has_metadata_stream &&
+ !ctf_trace->metadata_stream_sent_to_viewer) {
+ ctf_trace_put(ctf_trace);
+ continue;
+ }
+
+ cds_list_for_each_entry_rcu(relay_stream,
+ &ctf_trace->stream_list, stream_node)
+ {
+ struct relay_viewer_stream *viewer_stream;
+
+ if (!stream_get(relay_stream)) {
+ continue;
+ }
+
+ pthread_mutex_lock(&relay_stream->lock);
+ /*
+ * stream published is protected by the session lock.
+ */
+ if (!relay_stream->published) {
+ goto next;
+ }
+ viewer_stream = viewer_stream_get_by_id(
+ relay_stream->stream_handle);
+ if (!viewer_stream) {
+ struct lttng_trace_chunk *viewer_stream_trace_chunk = NULL;
+
+ /*
+ * Save that we sent the metadata stream to the
+ * viewer. So that we know what trace the viewer
+ * is aware of.
+ */
+ if (relay_stream->is_metadata) {
+ ctf_trace->metadata_stream_sent_to_viewer = true;
+ }
+
+ /*
+ * If a rotation is ongoing, use a copy of the
+ * relay stream's chunk to ensure the stream
+ * files exist.
+ *
+ * Otherwise, the viewer session's current trace
+ * chunk can be used safely.
+ */
+ if ((relay_stream->ongoing_rotation.is_set ||
+ relay_session->ongoing_rotation) &&
+ relay_stream->trace_chunk) {
+ viewer_stream_trace_chunk = lttng_trace_chunk_copy(
+ relay_stream->trace_chunk);
+ if (!viewer_stream_trace_chunk) {
+ ret = -1;
+ ctf_trace_put(ctf_trace);
+ goto error_unlock;
+ }
+ } else {
+ /*
+ * Transition the viewer session into the newest trace chunk available.
+ */
+ if (!lttng_trace_chunk_ids_equal(viewer_session->current_trace_chunk,
+ relay_stream->trace_chunk)) {
+
+ ret = viewer_session_set_trace_chunk_copy(
+ viewer_session,
+ relay_stream->trace_chunk);
+ if (ret) {
+ ret = -1;
+ ctf_trace_put(ctf_trace);
+ goto error_unlock;
+ }
+ }
+
+ if (relay_stream->trace_chunk) {
+ /*
+ * If the corresponding relay
+ * stream's trace chunk is set,
+ * the viewer stream will be
+ * created under it.
+ *
+ * Note that a relay stream can
+ * have a NULL output trace
+ * chunk (for instance, after a
+ * clear against a stopped
+ * session).
+ */
+ const bool reference_acquired = lttng_trace_chunk_get(
+ viewer_session->current_trace_chunk);
+
+ LTTNG_ASSERT(reference_acquired);
+ viewer_stream_trace_chunk =
+ viewer_session->current_trace_chunk;
+ }
+ }
+
+ viewer_stream = viewer_stream_create(
+ relay_stream,
+ viewer_stream_trace_chunk,
+ seek_t);
+ lttng_trace_chunk_put(viewer_stream_trace_chunk);
+ viewer_stream_trace_chunk = NULL;
+ if (!viewer_stream) {
+ ret = -1;
+ ctf_trace_put(ctf_trace);
+ goto error_unlock;
+ }
+
+ if (nb_created) {
+ /* Update number of created stream counter. */
+ (*nb_created)++;
+ }
+ /*
+ * Ensure a self-reference is preserved even
+ * after we have put our local reference.
+ */
+ if (!viewer_stream_get(viewer_stream)) {
+ ERR("Unable to get self-reference on viewer stream, logic error.");
+ abort();
+ }
+ } else {
+ if (!viewer_stream->sent_flag && nb_unsent) {
+ /* Update number of unsent stream counter. */
+ (*nb_unsent)++;
+ }
+ }
+ /* Update number of total stream counter. */
+ if (nb_total) {
+ if (relay_stream->is_metadata) {
+ if (!relay_stream->closed ||
+ relay_stream->metadata_received >
+ viewer_stream->metadata_sent) {
+ (*nb_total)++;
+ }
+ } else {
+ if (!relay_stream->closed ||
+ !(((int64_t)(relay_stream->prev_data_seq -
+ relay_stream->last_net_seq_num)) >=
+ 0)) {
+ (*nb_total)++;
+ }
+ }
+ }
+ /* Put local reference. */
+ viewer_stream_put(viewer_stream);
+ next:
+ pthread_mutex_unlock(&relay_stream->lock);
+ stream_put(relay_stream);
+ }
+ relay_stream = NULL;
+ ctf_trace_put(ctf_trace);
+ }
+
+ ret = 0;
+
+error_unlock:
+ rcu_read_unlock();
+
+ if (relay_stream) {
+ pthread_mutex_unlock(&relay_stream->lock);
+ stream_put(relay_stream);
+ }
+
+ return ret;
+}
+
+int relayd_live_stop(void)
+{
+ /* Stop dispatch thread */
+ CMM_STORE_SHARED(live_dispatch_thread_exit, 1);
+ futex_nto1_wake(&viewer_conn_queue.futex);
+ return 0;
+}
+
+/*
+ * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set.
+ */
+static
+int create_named_thread_poll_set(struct lttng_poll_event *events,
+ int size, const char *name)
+{
+ int ret;
+
+ if (events == NULL || size == 0) {
+ ret = -1;
+ goto error;
+ }
+
+ ret = fd_tracker_util_poll_create(the_fd_tracker,
+ name, events, 1, LTTNG_CLOEXEC);
+ if (ret) {
+ PERROR("Failed to create \"%s\" poll file descriptor", name);
+ goto error;
+ }
+
+ /* Add quit pipe */
+ ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR);
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return ret;
+}
+
+/*
+ * Check if the thread quit pipe was triggered.
+ *
+ * Return 1 if it was triggered else 0;
+ */
+static
+int check_thread_quit_pipe(int fd, uint32_t events)
+{
+ if (fd == thread_quit_pipe[0] && (events & LPOLLIN)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static
+int create_sock(void *data, int *out_fd)
+{
+ int ret;
+ struct lttcomm_sock *sock = (lttcomm_sock *) data;
+
+ ret = lttcomm_create_sock(sock);
+ if (ret < 0) {
+ goto end;
+ }
+
+ *out_fd = sock->fd;
+end:
+ return ret;
+}
+
+static
+int close_sock(void *data, int *in_fd)
+{
+ struct lttcomm_sock *sock = (lttcomm_sock *) data;
+
+ return sock->ops->close(sock);
+}
+
+static int accept_sock(void *data, int *out_fd)
+{
+ int ret = 0;
+ /* Socks is an array of in_sock, out_sock. */
+ struct lttcomm_sock **socks = (lttcomm_sock **) data;
+ struct lttcomm_sock *in_sock = socks[0];
+
+ socks[1] = in_sock->ops->accept(in_sock);
+ if (!socks[1]) {
+ ret = -1;
+ goto end;
+ }
+ *out_fd = socks[1]->fd;
+end:
+ return ret;
+}
+
+static
+struct lttcomm_sock *accept_live_sock(struct lttcomm_sock *listening_sock,
+ const char *name)
+{
+ int out_fd, ret;
+ struct lttcomm_sock *socks[2] = { listening_sock, NULL };
+ struct lttcomm_sock *new_sock = NULL;
+
+ ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &out_fd,
+ (const char **) &name, 1, accept_sock, &socks);
+ if (ret) {
+ goto end;
+ }
+ new_sock = socks[1];
+ DBG("%s accepted, socket %d", name, new_sock->fd);
+end:
+ return new_sock;
+}
+
+/*
+ * Create and init socket from uri.
+ */
+static
+struct lttcomm_sock *init_socket(struct lttng_uri *uri, const char *name)
+{
+ int ret, sock_fd;
+ struct lttcomm_sock *sock = NULL;
+ char uri_str[LTTNG_PATH_MAX];
+ char *formated_name = NULL;
+
+ sock = lttcomm_alloc_sock_from_uri(uri);
+ if (sock == NULL) {
+ ERR("Allocating socket");
+ goto error;
+ }
+
+ /*
+ * Don't fail to create the socket if the name can't be built as it is
+ * only used for debugging purposes.
+ */
+ ret = uri_to_str_url(uri, uri_str, sizeof(uri_str));
+ uri_str[sizeof(uri_str) - 1] = '\0';
+ if (ret >= 0) {
+ ret = asprintf(&formated_name, "%s socket @ %s", name,
+ uri_str);
+ if (ret < 0) {
+ formated_name = NULL;
+ }
+ }
+
+ ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd,
+ (const char **) (formated_name ? &formated_name : NULL),
+ 1, create_sock, sock);
+ if (ret) {
+ PERROR("Failed to create \"%s\" socket",
+ formated_name ?: "Unknown");
+ goto error;
+ }
+ DBG("Listening on %s socket %d", name, sock->fd);
+
+ ret = sock->ops->bind(sock);
+ if (ret < 0) {
+ PERROR("Failed to bind lttng-live socket");
+ goto error;
+ }
+
+ ret = sock->ops->listen(sock, -1);
+ if (ret < 0) {
+ goto error;
+
+ }
+
+ free(formated_name);
+ return sock;
+
+error:
+ if (sock) {
+ lttcomm_destroy_sock(sock);
+ }
+ free(formated_name);
+ return NULL;
+}
+
+/*
+ * This thread manages the listening for new connections on the network
+ */
+static
+void *thread_listener(void *data)
+{
+ int i, ret, pollfd, err = -1;
+ uint32_t revents, nb_fd;
+ struct lttng_poll_event events;
+ struct lttcomm_sock *live_control_sock;
+
+ DBG("[thread] Relay live listener started");
+
+ rcu_register_thread();
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_LISTENER);
+
+ health_code_update();
+
+ live_control_sock = init_socket(live_uri, "Live listener");
+ if (!live_control_sock) {
+ goto error_sock_control;
+ }
+
+ /* Pass 2 as size here for the thread quit pipe and control sockets. */
+ ret = create_named_thread_poll_set(&events, 2,
+ "Live listener thread epoll");
+ if (ret < 0) {
+ goto error_create_poll;
+ }
+
+ /* Add the control socket */
+ ret = lttng_poll_add(&events, live_control_sock->fd, LPOLLIN | LPOLLRDHUP);
+ if (ret < 0) {
+ goto error_poll_add;
+ }
+
+ lttng_relay_notify_ready();
+
+ if (testpoint(relayd_thread_live_listener)) {
+ goto error_testpoint;
+ }
+
+ while (1) {
+ health_code_update();
+
+ DBG("Listener accepting live viewers connections");
+
+restart:
+ health_poll_entry();
+ ret = lttng_poll_wait(&events, -1);
+ health_poll_exit();
+ if (ret < 0) {
+ /*
+ * Restart interrupted system call.
+ */
+ if (errno == EINTR) {
+ goto restart;
+ }
+ goto error;
+ }
+ nb_fd = ret;
+
+ DBG("Relay new viewer connection received");
+ for (i = 0; i < nb_fd; i++) {
+ health_code_update();
+
+ /* Fetch once the poll data */
+ revents = LTTNG_POLL_GETEV(&events, i);
+ pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ /* Thread quit pipe has been closed. Killing thread. */
+ ret = check_thread_quit_pipe(pollfd, revents);
+ if (ret) {
+ err = 0;
+ goto exit;
+ }
+
+ if (revents & LPOLLIN) {
+ /*
+ * A new connection is requested, therefore a
+ * viewer connection is allocated in this
+ * thread, enqueued to a global queue and
+ * dequeued (and freed) in the worker thread.
+ */
+ int val = 1;
+ struct relay_connection *new_conn;
+ struct lttcomm_sock *newsock;
+
+ newsock = accept_live_sock(live_control_sock,
+ "Live socket to client");
+ if (!newsock) {
+ PERROR("accepting control sock");
+ goto error;
+ }
+ DBG("Relay viewer connection accepted socket %d", newsock->fd);
+
+ ret = setsockopt(newsock->fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ sizeof(val));
+ if (ret < 0) {
+ PERROR("setsockopt inet");
+ lttcomm_destroy_sock(newsock);
+ goto error;
+ }
+ new_conn = connection_create(newsock, RELAY_CONNECTION_UNKNOWN);
+ if (!new_conn) {
+ lttcomm_destroy_sock(newsock);
+ goto error;
+ }
+ /* Ownership assumed by the connection. */
+ newsock = NULL;
+
+ /* Enqueue request for the dispatcher thread. */
+ cds_wfcq_head_ptr_t head;
+ head.h = &viewer_conn_queue.head;
+ cds_wfcq_enqueue(head, &viewer_conn_queue.tail,
+ &new_conn->qnode);
+
+ /*
+ * Wake the dispatch queue futex.
+ * Implicit memory barrier with the
+ * exchange in cds_wfcq_enqueue.
+ */
+ futex_nto1_wake(&viewer_conn_queue.futex);
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ ERR("socket poll error");
+ goto error;
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ goto error;
+ }
+ }
+ }
+
+exit:
+error:
+error_poll_add:
+error_testpoint:
+ (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
+error_create_poll:
+ if (live_control_sock->fd >= 0) {
+ int sock_fd = live_control_sock->fd;
+
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ &sock_fd, 1, close_sock,
+ live_control_sock);
+ if (ret) {
+ PERROR("close");
+ }
+ live_control_sock->fd = -1;
+ }
+ lttcomm_destroy_sock(live_control_sock);
+error_sock_control:
+ if (err) {
+ health_error();
+ DBG("Live viewer listener thread exited with error");
+ }
+ health_unregister(health_relayd);
+ rcu_unregister_thread();
+ DBG("Live viewer listener thread cleanup complete");
+ if (lttng_relay_stop_threads()) {
+ ERR("Error stopping threads");
+ }
+ return NULL;
+}
+
+/*
+ * This thread manages the dispatching of the requests to worker threads
+ */
+static
+void *thread_dispatcher(void *data)
+{
+ int err = -1;
+ ssize_t ret;
+ struct cds_wfcq_node *node;
+ struct relay_connection *conn = NULL;
+
+ DBG("[thread] Live viewer relay dispatcher started");
+
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_DISPATCHER);
+
+ if (testpoint(relayd_thread_live_dispatcher)) {
+ goto error_testpoint;
+ }
+
+ health_code_update();
+
+ for (;;) {
+ health_code_update();
+
+ /* Atomically prepare the queue futex */
+ futex_nto1_prepare(&viewer_conn_queue.futex);
+
+ if (CMM_LOAD_SHARED(live_dispatch_thread_exit)) {
+ break;
+ }
+
+ do {
+ health_code_update();
+
+ /* Dequeue commands */
+ node = cds_wfcq_dequeue_blocking(&viewer_conn_queue.head,
+ &viewer_conn_queue.tail);
+ if (node == NULL) {
+ DBG("Woken up but nothing in the live-viewer "
+ "relay command queue");
+ /* Continue thread execution */
+ break;
+ }
+ conn = caa_container_of(node, struct relay_connection, qnode);
+ DBG("Dispatching viewer request waiting on sock %d",
+ conn->sock->fd);
+
+ /*
+ * Inform worker thread of the new request. This
+ * call is blocking so we can be assured that
+ * the data will be read at some point in time
+ * or wait to the end of the world :)
+ */
+ ret = lttng_write(live_conn_pipe[1], &conn, sizeof(conn));
+ if (ret < 0) {
+ PERROR("write conn pipe");
+ connection_put(conn);
+ goto error;
+ }
+ } while (node != NULL);
+
+ /* Futex wait on queue. Blocking call on futex() */
+ health_poll_entry();
+ futex_nto1_wait(&viewer_conn_queue.futex);
+ health_poll_exit();
+ }
+
+ /* Normal exit, no error */
+ err = 0;
+
+error:
+error_testpoint:
+ if (err) {
+ health_error();
+ ERR("Health error occurred in %s", __func__);
+ }
+ health_unregister(health_relayd);
+ DBG("Live viewer dispatch thread dying");
+ if (lttng_relay_stop_threads()) {
+ ERR("Error stopping threads");
+ }
+ return NULL;
+}
+
+/*
+ * Establish connection with the viewer and check the versions.
+ *
+ * Return 0 on success or else negative value.
+ */
+static
+int viewer_connect(struct relay_connection *conn)
+{
+ int ret;
+ struct lttng_viewer_connect reply, msg;
+
+ conn->version_check_done = 1;
+
+ health_code_update();
+
+ DBG("Viewer is establishing a connection to the relayd.");
+
+ ret = recv_request(conn->sock, &msg, sizeof(msg));
+ if (ret < 0) {
+ goto end;
+ }
+
+ health_code_update();
+
+ memset(&reply, 0, sizeof(reply));
+ reply.major = RELAYD_VERSION_COMM_MAJOR;
+ reply.minor = RELAYD_VERSION_COMM_MINOR;
+
+ /* Major versions must be the same */
+ if (reply.major != be32toh(msg.major)) {
+ DBG("Incompatible major versions ([relayd] %u vs [client] %u)",
+ reply.major, be32toh(msg.major));
+ ret = -1;
+ goto end;
+ }
+
+ conn->major = reply.major;
+ /* We adapt to the lowest compatible version */
+ if (reply.minor <= be32toh(msg.minor)) {
+ conn->minor = reply.minor;
+ } else {
+ conn->minor = be32toh(msg.minor);
+ }
+
+ if (be32toh(msg.type) == LTTNG_VIEWER_CLIENT_COMMAND) {
+ conn->type = RELAY_VIEWER_COMMAND;
+ } else if (be32toh(msg.type) == LTTNG_VIEWER_CLIENT_NOTIFICATION) {
+ conn->type = RELAY_VIEWER_NOTIFICATION;
+ } else {
+ ERR("Unknown connection type : %u", be32toh(msg.type));
+ ret = -1;
+ goto end;
+ }
+
+ reply.major = htobe32(reply.major);
+ reply.minor = htobe32(reply.minor);
+ if (conn->type == RELAY_VIEWER_COMMAND) {
+ /*
+ * Increment outside of htobe64 macro, because the argument can
+ * be used more than once within the macro, and thus the
+ * operation may be undefined.
+ */
+ pthread_mutex_lock(&last_relay_viewer_session_id_lock);
+ last_relay_viewer_session_id++;
+ pthread_mutex_unlock(&last_relay_viewer_session_id_lock);
+ reply.viewer_session_id = htobe64(last_relay_viewer_session_id);
+ }
+
+ health_code_update();
+
+ ret = send_response(conn->sock, &reply, sizeof(reply));
+ if (ret < 0) {
+ goto end;
+ }
+
+ health_code_update();
+
+ DBG("Version check done using protocol %u.%u", conn->major, conn->minor);
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/*
+ * Send the viewer the list of current sessions.
+ * We need to create a copy of the hash table content because otherwise
+ * we cannot assume the number of entries stays the same between getting
+ * the number of HT elements and iteration over the HT.
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+int viewer_list_sessions(struct relay_connection *conn)
+{
+ int ret = 0;
+ struct lttng_viewer_list_sessions session_list;
+ struct lttng_ht_iter iter;
+ struct relay_session *session;
+ struct lttng_viewer_session *send_session_buf = NULL;
+ uint32_t buf_count = SESSION_BUF_DEFAULT_COUNT;
+ uint32_t count = 0;
+
+ DBG("List sessions received");
+
+ send_session_buf = (lttng_viewer_session *) zmalloc(SESSION_BUF_DEFAULT_COUNT * sizeof(*send_session_buf));
+ if (!send_session_buf) {
+ return -1;
+ }
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(sessions_ht->ht, &iter.iter, session,
+ session_n.node) {
+ struct lttng_viewer_session *send_session;
+
+ health_code_update();
+
+ pthread_mutex_lock(&session->lock);
+ if (session->connection_closed) {
+ /* Skip closed session */
+ goto next_session;
+ }
+
+ if (count >= buf_count) {
+ struct lttng_viewer_session *newbuf;
+ uint32_t new_buf_count = buf_count << 1;
+
+ newbuf = (lttng_viewer_session *) realloc(send_session_buf,
+ new_buf_count * sizeof(*send_session_buf));
+ if (!newbuf) {
+ ret = -1;
+ goto break_loop;
+ }
+ send_session_buf = newbuf;
+ buf_count = new_buf_count;
+ }
+ send_session = &send_session_buf[count];
+ if (lttng_strncpy(send_session->session_name,
+ session->session_name,
+ sizeof(send_session->session_name))) {
+ ret = -1;
+ goto break_loop;
+ }
+ if (lttng_strncpy(send_session->hostname, session->hostname,
+ sizeof(send_session->hostname))) {
+ ret = -1;
+ goto break_loop;
+ }
+ send_session->id = htobe64(session->id);
+ send_session->live_timer = htobe32(session->live_timer);
+ if (session->viewer_attached) {
+ send_session->clients = htobe32(1);
+ } else {
+ send_session->clients = htobe32(0);
+ }
+ send_session->streams = htobe32(session->stream_count);
+ count++;
+ next_session:
+ pthread_mutex_unlock(&session->lock);
+ continue;
+ break_loop:
+ pthread_mutex_unlock(&session->lock);
+ break;
+ }
+ rcu_read_unlock();
+ if (ret < 0) {
+ goto end_free;
+ }
+
+ session_list.sessions_count = htobe32(count);
+
+ health_code_update();
+
+ ret = send_response(conn->sock, &session_list, sizeof(session_list));
+ if (ret < 0) {
+ goto end_free;
+ }
+
+ health_code_update();
+
+ ret = send_response(conn->sock, send_session_buf,
+ count * sizeof(*send_session_buf));
+ if (ret < 0) {
+ goto end_free;
+ }
+ health_code_update();
+
+ ret = 0;
+end_free:
+ free(send_session_buf);
+ return ret;
+}
+
+/*
+ * Send the viewer the list of current streams.
+ */
+static
+int viewer_get_new_streams(struct relay_connection *conn)
+{
+ int ret, send_streams = 0;
+ uint32_t nb_created = 0, nb_unsent = 0, nb_streams = 0, nb_total = 0;
+ struct lttng_viewer_new_streams_request request;
+ struct lttng_viewer_new_streams_response response;
+ struct relay_session *session = NULL;
+ uint64_t session_id;
+ bool closed = false;
+
+ LTTNG_ASSERT(conn);
+
+ DBG("Get new streams received");
+
+ health_code_update();
+
+ /* Receive the request from the connected client. */
+ ret = recv_request(conn->sock, &request, sizeof(request));
+ if (ret < 0) {
+ goto error;
+ }
+ session_id = be64toh(request.session_id);
+
+ health_code_update();
+
+ memset(&response, 0, sizeof(response));
+
+ session = session_get_by_id(session_id);
+ if (!session) {
+ DBG("Relay session %" PRIu64 " not found", session_id);
+ response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_ERR);
+ goto send_reply;
+ }
+
+ if (!viewer_session_is_attached(conn->viewer_session, session)) {
+ response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_ERR);
+ goto send_reply;
+ }
+
+ /*
+ * For any new stream, create it with LTTNG_VIEWER_SEEK_BEGINNING since
+ * that at this point the client is already attached to the session.Aany
+ * initial stream will have been created with the seek type at attach
+ * time (for now most readers use the LTTNG_VIEWER_SEEK_LAST on attach).
+ * Otherwise any event happening in a new stream between the attach and
+ * a call to viewer_get_new_streams will be "lost" (never received) from
+ * the viewer's point of view.
+ */
+ pthread_mutex_lock(&session->lock);
+ ret = make_viewer_streams(session,
+ conn->viewer_session,
+ LTTNG_VIEWER_SEEK_BEGINNING, &nb_total, &nb_unsent,
+ &nb_created, &closed);
+ if (ret < 0) {
+ goto error_unlock_session;
+ }
+ send_streams = 1;
+ response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_OK);
+
+ /* Only send back the newly created streams with the unsent ones. */
+ nb_streams = nb_created + nb_unsent;
+ response.streams_count = htobe32(nb_streams);
+
+ /*
+ * If the session is closed, HUP when there are no more streams
+ * with data.
+ */
+ if (closed && nb_total == 0) {
+ send_streams = 0;
+ response.streams_count = 0;
+ response.status = htobe32(LTTNG_VIEWER_NEW_STREAMS_HUP);
+ goto send_reply_unlock;
+ }
+send_reply_unlock:
+ pthread_mutex_unlock(&session->lock);
+
+send_reply:
+ health_code_update();
+ ret = send_response(conn->sock, &response, sizeof(response));
+ if (ret < 0) {
+ goto end_put_session;
+ }
+ health_code_update();
+
+ /*
+ * Unknown or empty session, just return gracefully, the viewer
+ * knows what is happening.
+ */
+ if (!send_streams || !nb_streams) {
+ ret = 0;
+ goto end_put_session;
+ }
+
+ /*
+ * Send stream and *DON'T* ignore the sent flag so every viewer
+ * streams that were not sent from that point will be sent to
+ * the viewer.
+ */
+ ret = send_viewer_streams(conn->sock, session_id, 0);
+ if (ret < 0) {
+ goto end_put_session;
+ }
+
+end_put_session:
+ if (session) {
+ session_put(session);
+ }
+error:
+ return ret;
+error_unlock_session:
+ pthread_mutex_unlock(&session->lock);
+ session_put(session);
+ return ret;
+}
+
+/*
+ * Send the viewer the list of current sessions.
+ */
+static
+int viewer_attach_session(struct relay_connection *conn)
+{
+ int send_streams = 0;
+ ssize_t ret;
+ uint32_t nb_streams = 0;
+ enum lttng_viewer_seek seek_type;
+ struct lttng_viewer_attach_session_request request;
+ struct lttng_viewer_attach_session_response response;
+ struct relay_session *session = NULL;
+ enum lttng_viewer_attach_return_code viewer_attach_status;
+ bool closed = false;
+ uint64_t session_id;
+
+ LTTNG_ASSERT(conn);
+
+ health_code_update();
+
+ /* Receive the request from the connected client. */
+ ret = recv_request(conn->sock, &request, sizeof(request));
+ if (ret < 0) {
+ goto error;
+ }
+
+ session_id = be64toh(request.session_id);
+ health_code_update();
+
+ memset(&response, 0, sizeof(response));
+
+ if (!conn->viewer_session) {
+ DBG("Client trying to attach before creating a live viewer session");
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_NO_SESSION);
+ goto send_reply;
+ }
+
+ session = session_get_by_id(session_id);
+ if (!session) {
+ DBG("Relay session %" PRIu64 " not found", session_id);
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_UNK);
+ goto send_reply;
+ }
+ DBG("Attach session ID %" PRIu64 " received", session_id);
+
+ pthread_mutex_lock(&session->lock);
+ if (session->live_timer == 0) {
+ DBG("Not live session");
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_NOT_LIVE);
+ goto send_reply;
+ }
+
+ send_streams = 1;
+ viewer_attach_status = viewer_session_attach(conn->viewer_session,
+ session);
+ if (viewer_attach_status != LTTNG_VIEWER_ATTACH_OK) {
+ response.status = htobe32(viewer_attach_status);
+ goto send_reply;
+ }
+
+ switch (be32toh(request.seek)) {
+ case LTTNG_VIEWER_SEEK_BEGINNING:
+ case LTTNG_VIEWER_SEEK_LAST:
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_OK);
+ seek_type = (lttng_viewer_seek) be32toh(request.seek);
+ break;
+ default:
+ ERR("Wrong seek parameter");
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_SEEK_ERR);
+ send_streams = 0;
+ goto send_reply;
+ }
+
+ ret = make_viewer_streams(session,
+ conn->viewer_session, seek_type,
+ &nb_streams, NULL, NULL, &closed);
+ if (ret < 0) {
+ goto end_put_session;
+ }
+ pthread_mutex_unlock(&session->lock);
+ session_put(session);
+ session = NULL;
+
+ response.streams_count = htobe32(nb_streams);
+ /*
+ * If the session is closed when the viewer is attaching, it
+ * means some of the streams may have been concurrently removed,
+ * so we don't allow the viewer to attach, even if there are
+ * streams available.
+ */
+ if (closed) {
+ send_streams = 0;
+ response.streams_count = 0;
+ response.status = htobe32(LTTNG_VIEWER_ATTACH_UNK);
+ goto send_reply;
+ }
+
+send_reply:
+ health_code_update();
+ ret = send_response(conn->sock, &response, sizeof(response));
+ if (ret < 0) {
+ goto end_put_session;
+ }
+ health_code_update();
+
+ /*
+ * Unknown or empty session, just return gracefully, the viewer
+ * knows what is happening.
+ */
+ if (!send_streams || !nb_streams) {
+ ret = 0;
+ goto end_put_session;
+ }
+
+ /* Send stream and ignore the sent flag. */
+ ret = send_viewer_streams(conn->sock, session_id, 1);
+ if (ret < 0) {
+ goto end_put_session;
+ }
+
+end_put_session:
+ if (session) {
+ pthread_mutex_unlock(&session->lock);
+ session_put(session);
+ }
+error:
+ return ret;
+}
+
+/*
+ * Open the index file if needed for the given vstream.
+ *
+ * If an index file is successfully opened, the vstream will set it as its
+ * current index file.
+ *
+ * Return 0 on success, a negative value on error (-ENOENT if not ready yet).
+ *
+ * Called with rstream lock held.
+ */
+static int try_open_index(struct relay_viewer_stream *vstream,
+ struct relay_stream *rstream)
+{
+ int ret = 0;
+ const uint32_t connection_major = rstream->trace->session->major;
+ const uint32_t connection_minor = rstream->trace->session->minor;
+ enum lttng_trace_chunk_status chunk_status;
+
+ if (vstream->index_file) {
+ goto end;
+ }
+
+ /*
+ * First time, we open the index file and at least one index is ready.
+ */
+ if (rstream->index_received_seqcount == 0 ||
+ !vstream->stream_file.trace_chunk) {
+ ret = -ENOENT;
+ goto end;
+ }
+
+ chunk_status = lttng_index_file_create_from_trace_chunk_read_only(
+ vstream->stream_file.trace_chunk, rstream->path_name,
+ rstream->channel_name, rstream->tracefile_size,
+ vstream->current_tracefile_id,
+ lttng_to_index_major(connection_major, connection_minor),
+ lttng_to_index_minor(connection_major, connection_minor),
+ true, &vstream->index_file);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
+ ret = -ENOENT;
+ } else {
+ ret = -1;
+ }
+ }
+
+end:
+ return ret;
+}
+
+/*
+ * Check the status of the index for the given stream. This function
+ * updates the index structure if needed and can put (close) the vstream
+ * in the HUP situation.
+ *
+ * Return 0 means that we can proceed with the index. A value of 1 means
+ * that the index has been updated and is ready to be sent to the
+ * client. A negative value indicates an error that can't be handled.
+ *
+ * Called with rstream lock held.
+ */
+static int check_index_status(struct relay_viewer_stream *vstream,
+ struct relay_stream *rstream, struct ctf_trace *trace,
+ struct lttng_viewer_index *index)
+{
+ int ret;
+
+ DBG("Check index status: index_received_seqcount %" PRIu64 " "
+ "index_sent_seqcount %" PRIu64 " "
+ "for stream %" PRIu64,
+ rstream->index_received_seqcount,
+ vstream->index_sent_seqcount,
+ vstream->stream->stream_handle);
+ if ((trace->session->connection_closed || rstream->closed)
+ && rstream->index_received_seqcount
+ == vstream->index_sent_seqcount) {
+ /*
+ * Last index sent and session connection or relay
+ * stream are closed.
+ */
+ index->status = htobe32(LTTNG_VIEWER_INDEX_HUP);
+ goto hup;
+ } else if (rstream->beacon_ts_end != -1ULL &&
+ (rstream->index_received_seqcount == 0 ||
+ (vstream->index_sent_seqcount != 0 &&
+ rstream->index_received_seqcount
+ <= vstream->index_sent_seqcount))) {
+ /*
+ * We've received a synchronization beacon and the last index
+ * available has been sent, the index for now is inactive.
+ *
+ * In this case, we have received a beacon which allows us to
+ * inform the client of a time interval during which we can
+ * guarantee that there are no events to read (and never will
+ * be).
+ *
+ * The sent seqcount can grow higher than receive seqcount on
+ * clear because the rotation performed by clear will push
+ * the index_sent_seqcount ahead (see
+ * viewer_stream_sync_tracefile_array_tail) and skip over
+ * packet sequence numbers.
+ */
+ index->status = htobe32(LTTNG_VIEWER_INDEX_INACTIVE);
+ index->timestamp_end = htobe64(rstream->beacon_ts_end);
+ index->stream_id = htobe64(rstream->ctf_stream_id);
+ DBG("Check index status: inactive with beacon, for stream %" PRIu64,
+ vstream->stream->stream_handle);
+ goto index_ready;
+ } else if (rstream->index_received_seqcount == 0 ||
+ (vstream->index_sent_seqcount != 0 &&
+ rstream->index_received_seqcount
+ <= vstream->index_sent_seqcount)) {
+ /*
+ * This checks whether received <= sent seqcount. In
+ * this case, we have not received a beacon. Therefore,
+ * we can only ask the client to retry later.
+ *
+ * The sent seqcount can grow higher than receive seqcount on
+ * clear because the rotation performed by clear will push
+ * the index_sent_seqcount ahead (see
+ * viewer_stream_sync_tracefile_array_tail) and skip over
+ * packet sequence numbers.
+ */
+ index->status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
+ DBG("Check index status: retry for stream %" PRIu64,
+ vstream->stream->stream_handle);
+ goto index_ready;
+ } else if (!tracefile_array_seq_in_file(rstream->tfa,
+ vstream->current_tracefile_id,
+ vstream->index_sent_seqcount)) {
+ /*
+ * The next index we want to send cannot be read either
+ * because we need to perform a rotation, or due to
+ * the producer having overwritten its trace file.
+ */
+ DBG("Viewer stream %" PRIu64 " rotation",
+ vstream->stream->stream_handle);
+ ret = viewer_stream_rotate(vstream);
+ if (ret == 1) {
+ /* EOF across entire stream. */
+ index->status = htobe32(LTTNG_VIEWER_INDEX_HUP);
+ goto hup;
+ }
+ /*
+ * If we have been pushed due to overwrite, it
+ * necessarily means there is data that can be read in
+ * the stream. If we rotated because we reached the end
+ * of a tracefile, it means the following tracefile
+ * needs to contain at least one index, else we would
+ * have already returned LTTNG_VIEWER_INDEX_RETRY to the
+ * viewer. The updated index_sent_seqcount needs to
+ * point to a readable index entry now.
+ *
+ * In the case where we "rotate" on a single file, we
+ * can end up in a case where the requested index is
+ * still unavailable.
+ */
+ if (rstream->tracefile_count == 1 &&
+ !tracefile_array_seq_in_file(
+ rstream->tfa,
+ vstream->current_tracefile_id,
+ vstream->index_sent_seqcount)) {
+ index->status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
+ DBG("Check index status: retry: "
+ "tracefile array sequence number %" PRIu64
+ " not in file for stream %" PRIu64,
+ vstream->index_sent_seqcount,
+ vstream->stream->stream_handle);
+ goto index_ready;
+ }
+ LTTNG_ASSERT(tracefile_array_seq_in_file(rstream->tfa,
+ vstream->current_tracefile_id,
+ vstream->index_sent_seqcount));
+ }
+ /* ret == 0 means successful so we continue. */
+ ret = 0;
+ return ret;
+
+hup:
+ viewer_stream_put(vstream);
+index_ready:
+ return 1;
+}
+
+static
+void viewer_stream_rotate_to_trace_chunk(struct relay_viewer_stream *vstream,
+ struct lttng_trace_chunk *new_trace_chunk)
+{
+ lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
+
+ if (new_trace_chunk) {
+ const bool acquired_reference = lttng_trace_chunk_get(
+ new_trace_chunk);
+
+ LTTNG_ASSERT(acquired_reference);
+ }
+
+ vstream->stream_file.trace_chunk = new_trace_chunk;
+ viewer_stream_sync_tracefile_array_tail(vstream);
+ viewer_stream_close_files(vstream);
+}
+
+/*
+ * Send the next index for a stream.
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+int viewer_get_next_index(struct relay_connection *conn)
+{
+ int ret;
+ struct lttng_viewer_get_next_index request_index;
+ struct lttng_viewer_index viewer_index;
+ struct ctf_packet_index packet_index;
+ struct relay_viewer_stream *vstream = NULL;
+ struct relay_stream *rstream = NULL;
+ struct ctf_trace *ctf_trace = NULL;
+ struct relay_viewer_stream *metadata_viewer_stream = NULL;
+
+ LTTNG_ASSERT(conn);
+
+ DBG("Viewer get next index");
+
+ memset(&viewer_index, 0, sizeof(viewer_index));
+ health_code_update();
+
+ ret = recv_request(conn->sock, &request_index, sizeof(request_index));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+
+ vstream = viewer_stream_get_by_id(be64toh(request_index.stream_id));
+ if (!vstream) {
+ DBG("Client requested index of unknown stream id %" PRIu64,
+ (uint64_t) be64toh(request_index.stream_id));
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
+ goto send_reply;
+ }
+
+ /* Use back. ref. Protected by refcounts. */
+ rstream = vstream->stream;
+ ctf_trace = rstream->trace;
+
+ /* metadata_viewer_stream may be NULL. */
+ metadata_viewer_stream =
+ ctf_trace_get_viewer_metadata_stream(ctf_trace);
+
+ pthread_mutex_lock(&rstream->lock);
+
+ /*
+ * The viewer should not ask for index on metadata stream.
+ */
+ if (rstream->is_metadata) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
+ goto send_reply;
+ }
+
+ if (rstream->ongoing_rotation.is_set) {
+ /* Rotation is ongoing, try again later. */
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
+ goto send_reply;
+ }
+
+ if (rstream->trace->session->ongoing_rotation) {
+ /* Rotation is ongoing, try again later. */
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
+ goto send_reply;
+ }
+
+ /*
+ * Transition the viewer session into the newest trace chunk available.
+ */
+ if (!lttng_trace_chunk_ids_equal(
+ conn->viewer_session->current_trace_chunk,
+ rstream->trace_chunk)) {
+ DBG("Relay stream and viewer chunk ids differ");
+
+ ret = viewer_session_set_trace_chunk_copy(
+ conn->viewer_session,
+ rstream->trace_chunk);
+ if (ret) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
+ goto send_reply;
+ }
+ }
+
+ /*
+ * Transition the viewer stream into the latest trace chunk available.
+ *
+ * Note that the stream must _not_ rotate in one precise condition:
+ * the relay stream has rotated to a NULL trace chunk and the viewer
+ * stream is consuming the trace chunk that was active just before
+ * that rotation to NULL.
+ *
+ * This allows clients to consume all the packets of a trace chunk
+ * after a session's destruction.
+ */
+ if (conn->viewer_session->current_trace_chunk != vstream->stream_file.trace_chunk &&
+ !(rstream->completed_rotation_count == vstream->last_seen_rotation_count + 1 && !rstream->trace_chunk)) {
+ DBG("Viewer session and viewer stream chunk differ: "
+ "vsession chunk %p vstream chunk %p",
+ conn->viewer_session->current_trace_chunk,
+ vstream->stream_file.trace_chunk);
+ viewer_stream_rotate_to_trace_chunk(vstream,
+ conn->viewer_session->current_trace_chunk);
+ vstream->last_seen_rotation_count =
+ rstream->completed_rotation_count;
+ }
+
+ ret = check_index_status(vstream, rstream, ctf_trace, &viewer_index);
+ if (ret < 0) {
+ goto error_put;
+ } else if (ret == 1) {
+ /*
+ * We have no index to send and check_index_status has populated
+ * viewer_index's status.
+ */
+ goto send_reply;
+ }
+ /* At this point, ret is 0 thus we will be able to read the index. */
+ LTTNG_ASSERT(!ret);
+
+ /* Try to open an index if one is needed for that stream. */
+ ret = try_open_index(vstream, rstream);
+ if (ret == -ENOENT) {
+ if (rstream->closed) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
+ goto send_reply;
+ } else {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_RETRY);
+ goto send_reply;
+ }
+ }
+ if (ret < 0) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
+ goto send_reply;
+ }
+
+ /*
+ * vstream->stream_fd may be NULL if it has been closed by
+ * tracefile rotation, or if we are at the beginning of the
+ * stream. We open the data stream file here to protect against
+ * overwrite caused by tracefile rotation (in association with
+ * unlink performed before overwrite).
+ */
+ if (!vstream->stream_file.handle) {
+ char file_path[LTTNG_PATH_MAX];
+ enum lttng_trace_chunk_status status;
+ struct fs_handle *fs_handle;
+
+ ret = utils_stream_file_path(rstream->path_name,
+ rstream->channel_name, rstream->tracefile_size,
+ vstream->current_tracefile_id, NULL, file_path,
+ sizeof(file_path));
+ if (ret < 0) {
+ goto error_put;
+ }
+
+ /*
+ * It is possible the the file we are trying to open is
+ * missing if the stream has been closed (application exits with
+ * per-pid buffers) and a clear command has been performed.
+ */
+ status = lttng_trace_chunk_open_fs_handle(
+ vstream->stream_file.trace_chunk,
+ file_path, O_RDONLY, 0, &fs_handle, true);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ if (status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE &&
+ rstream->closed) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_HUP);
+ goto send_reply;
+ }
+ PERROR("Failed to open trace file for viewer stream");
+ goto error_put;
+ }
+ vstream->stream_file.handle = fs_handle;
+ }
+
+ ret = check_new_streams(conn);
+ if (ret < 0) {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
+ goto send_reply;
+ } else if (ret == 1) {
+ viewer_index.flags |= LTTNG_VIEWER_FLAG_NEW_STREAM;
+ }
+
+ ret = lttng_index_file_read(vstream->index_file, &packet_index);
+ if (ret) {
+ ERR("Relay error reading index file");
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_ERR);
+ goto send_reply;
+ } else {
+ viewer_index.status = htobe32(LTTNG_VIEWER_INDEX_OK);
+ vstream->index_sent_seqcount++;
+ }
+
+ /*
+ * Indexes are stored in big endian, no need to switch before sending.
+ */
+ DBG("Sending viewer index for stream %" PRIu64 " offset %" PRIu64,
+ rstream->stream_handle,
+ (uint64_t) be64toh(packet_index.offset));
+ viewer_index.offset = packet_index.offset;
+ viewer_index.packet_size = packet_index.packet_size;
+ viewer_index.content_size = packet_index.content_size;
+ viewer_index.timestamp_begin = packet_index.timestamp_begin;
+ viewer_index.timestamp_end = packet_index.timestamp_end;
+ viewer_index.events_discarded = packet_index.events_discarded;
+ viewer_index.stream_id = packet_index.stream_id;
+
+send_reply:
+ if (rstream) {
+ pthread_mutex_unlock(&rstream->lock);
+ }
+
+ if (metadata_viewer_stream) {
+ pthread_mutex_lock(&metadata_viewer_stream->stream->lock);
+ DBG("get next index metadata check: recv %" PRIu64
+ " sent %" PRIu64,
+ metadata_viewer_stream->stream->metadata_received,
+ metadata_viewer_stream->metadata_sent);
+ if (!metadata_viewer_stream->stream->metadata_received ||
+ metadata_viewer_stream->stream->metadata_received >
+ metadata_viewer_stream->metadata_sent) {
+ viewer_index.flags |= LTTNG_VIEWER_FLAG_NEW_METADATA;
+ }
+ pthread_mutex_unlock(&metadata_viewer_stream->stream->lock);
+ }
+
+ viewer_index.flags = htobe32(viewer_index.flags);
+ health_code_update();
+
+ ret = send_response(conn->sock, &viewer_index, sizeof(viewer_index));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+
+ if (vstream) {
+ DBG("Index %" PRIu64 " for stream %" PRIu64 " sent",
+ vstream->index_sent_seqcount,
+ vstream->stream->stream_handle);
+ }
+end:
+ if (metadata_viewer_stream) {
+ viewer_stream_put(metadata_viewer_stream);
+ }
+ if (vstream) {
+ viewer_stream_put(vstream);
+ }
+ return ret;
+
+error_put:
+ pthread_mutex_unlock(&rstream->lock);
+ if (metadata_viewer_stream) {
+ viewer_stream_put(metadata_viewer_stream);
+ }
+ viewer_stream_put(vstream);
+ return ret;
+}
+
+/*
+ * Send the next index for a stream
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+int viewer_get_packet(struct relay_connection *conn)
+{
+ int ret;
+ off_t lseek_ret;
+ char *reply = NULL;
+ struct lttng_viewer_get_packet get_packet_info;
+ struct lttng_viewer_trace_packet reply_header;
+ struct relay_viewer_stream *vstream = NULL;
+ uint32_t reply_size = sizeof(reply_header);
+ uint32_t packet_data_len = 0;
+ ssize_t read_len;
+ uint64_t stream_id;
+
+ DBG2("Relay get data packet");
+
+ health_code_update();
+
+ ret = recv_request(conn->sock, &get_packet_info,
+ sizeof(get_packet_info));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+
+ /* From this point on, the error label can be reached. */
+ memset(&reply_header, 0, sizeof(reply_header));
+ stream_id = (uint64_t) be64toh(get_packet_info.stream_id);
+
+ vstream = viewer_stream_get_by_id(stream_id);
+ if (!vstream) {
+ DBG("Client requested packet of unknown stream id %" PRIu64,
+ stream_id);
+ reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_ERR);
+ goto send_reply_nolock;
+ } else {
+ packet_data_len = be32toh(get_packet_info.len);
+ reply_size += packet_data_len;
+ }
+
+ reply = (char *) zmalloc(reply_size);
+ if (!reply) {
+ PERROR("packet reply zmalloc");
+ reply_size = sizeof(reply_header);
+ goto error;
+ }
+
+ pthread_mutex_lock(&vstream->stream->lock);
+ lseek_ret = fs_handle_seek(vstream->stream_file.handle,
+ be64toh(get_packet_info.offset), SEEK_SET);
+ if (lseek_ret < 0) {
+ PERROR("Failed to seek file system handle of viewer stream %" PRIu64
+ " to offset %" PRIu64,
+ stream_id,
+ (uint64_t) be64toh(get_packet_info.offset));
+ goto error;
+ }
+ read_len = fs_handle_read(vstream->stream_file.handle,
+ reply + sizeof(reply_header), packet_data_len);
+ if (read_len < packet_data_len) {
+ PERROR("Failed to read from file system handle of viewer stream id %" PRIu64
+ ", offset: %" PRIu64,
+ stream_id,
+ (uint64_t) be64toh(get_packet_info.offset));
+ goto error;
+ }
+ reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_OK);
+ reply_header.len = htobe32(packet_data_len);
+ goto send_reply;
+
+error:
+ /* No payload to send on error. */
+ reply_size = sizeof(reply_header);
+ reply_header.status = htobe32(LTTNG_VIEWER_GET_PACKET_ERR);
+
+send_reply:
+ if (vstream) {
+ pthread_mutex_unlock(&vstream->stream->lock);
+ }
+send_reply_nolock:
+
+ health_code_update();
+
+ if (reply) {
+ memcpy(reply, &reply_header, sizeof(reply_header));
+ ret = send_response(conn->sock, reply, reply_size);
+ } else {
+ /* No reply to send. */
+ ret = send_response(conn->sock, &reply_header,
+ reply_size);
+ }
+
+ health_code_update();
+ if (ret < 0) {
+ PERROR("sendmsg of packet data failed");
+ goto end_free;
+ }
+
+ DBG("Sent %u bytes for stream %" PRIu64, reply_size, stream_id);
+
+end_free:
+ free(reply);
+end:
+ if (vstream) {
+ viewer_stream_put(vstream);
+ }
+ return ret;
+}
+
+/*
+ * Send the session's metadata
+ *
+ * Return 0 on success else a negative value.
+ */
+static
+int viewer_get_metadata(struct relay_connection *conn)
+{
+ int ret = 0;
+ int fd = -1;
+ ssize_t read_len;
+ uint64_t len = 0;
+ char *data = NULL;
+ struct lttng_viewer_get_metadata request;
+ struct lttng_viewer_metadata_packet reply;
+ struct relay_viewer_stream *vstream = NULL;
+
+ LTTNG_ASSERT(conn);
+
+ DBG("Relay get metadata");
+
+ health_code_update();
+
+ ret = recv_request(conn->sock, &request, sizeof(request));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+
+ memset(&reply, 0, sizeof(reply));
+
+ vstream = viewer_stream_get_by_id(be64toh(request.stream_id));
+ if (!vstream) {
+ /*
+ * The metadata stream can be closed by a CLOSE command
+ * just before we attach. It can also be closed by
+ * per-pid tracing during tracing. Therefore, it is
+ * possible that we cannot find this viewer stream.
+ * Reply back to the client with an error if we cannot
+ * find it.
+ */
+ DBG("Client requested metadata of unknown stream id %" PRIu64,
+ (uint64_t) be64toh(request.stream_id));
+ reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
+ goto send_reply;
+ }
+ pthread_mutex_lock(&vstream->stream->lock);
+ if (!vstream->stream->is_metadata) {
+ ERR("Invalid metadata stream");
+ goto error;
+ }
+
+ if (vstream->metadata_sent >= vstream->stream->metadata_received) {
+ /*
+ * The live viewers expect to receive a NO_NEW_METADATA
+ * status before a stream disappears, otherwise they abort the
+ * entire live connection when receiving an error status.
+ *
+ * Clear feature resets the metadata_sent to 0 until the
+ * same metadata is received again.
+ */
+ reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
+ /*
+ * The live viewer considers a closed 0 byte metadata stream as
+ * an error.
+ */
+ if (vstream->metadata_sent > 0) {
+ vstream->stream->no_new_metadata_notified = true;
+ if (vstream->stream->closed) {
+ /* Release ownership for the viewer metadata stream. */
+ viewer_stream_put(vstream);
+ }
+ }
+ goto send_reply;
+ }
+
+ if (vstream->stream->trace_chunk &&
+ !lttng_trace_chunk_ids_equal(
+ conn->viewer_session->current_trace_chunk,
+ vstream->stream->trace_chunk)) {
+ /* A rotation has occurred on the relay stream. */
+ DBG("Metadata relay stream and viewer chunk ids differ");
+
+ ret = viewer_session_set_trace_chunk_copy(
+ conn->viewer_session,
+ vstream->stream->trace_chunk);
+ if (ret) {
+ reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
+ goto send_reply;
+ }
+ }
+
+ if (conn->viewer_session->current_trace_chunk &&
+ conn->viewer_session->current_trace_chunk !=
+ vstream->stream_file.trace_chunk) {
+ bool acquired_reference;
+
+ DBG("Viewer session and viewer stream chunk differ: "
+ "vsession chunk %p vstream chunk %p",
+ conn->viewer_session->current_trace_chunk,
+ vstream->stream_file.trace_chunk);
+ lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
+ acquired_reference = lttng_trace_chunk_get(conn->viewer_session->current_trace_chunk);
+ LTTNG_ASSERT(acquired_reference);
+ vstream->stream_file.trace_chunk =
+ conn->viewer_session->current_trace_chunk;
+ viewer_stream_close_files(vstream);
+ }
+
+ len = vstream->stream->metadata_received - vstream->metadata_sent;
+
+ if (!vstream->stream_file.trace_chunk) {
+ reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
+ len = 0;
+ goto send_reply;
+ } else if (vstream->stream_file.trace_chunk &&
+ !vstream->stream_file.handle && len > 0) {
+ /*
+ * Either this is the first time the metadata file is read, or a
+ * rotation of the corresponding relay stream has occurred.
+ */
+ struct fs_handle *fs_handle;
+ char file_path[LTTNG_PATH_MAX];
+ enum lttng_trace_chunk_status status;
+ struct relay_stream *rstream = vstream->stream;
+
+ ret = utils_stream_file_path(rstream->path_name,
+ rstream->channel_name, rstream->tracefile_size,
+ vstream->current_tracefile_id, NULL, file_path,
+ sizeof(file_path));
+ if (ret < 0) {
+ goto error;
+ }
+
+ /*
+ * It is possible the the metadata file we are trying to open is
+ * missing if the stream has been closed (application exits with
+ * per-pid buffers) and a clear command has been performed.
+ */
+ status = lttng_trace_chunk_open_fs_handle(
+ vstream->stream_file.trace_chunk,
+ file_path, O_RDONLY, 0, &fs_handle, true);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ if (status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
+ reply.status = htobe32(LTTNG_VIEWER_NO_NEW_METADATA);
+ len = 0;
+ if (vstream->stream->closed) {
+ viewer_stream_put(vstream);
+ }
+ goto send_reply;
+ }
+ PERROR("Failed to open metadata file for viewer stream");
+ goto error;
+ }
+ vstream->stream_file.handle = fs_handle;
+
+ if (vstream->metadata_sent != 0) {
+ /*
+ * The client does not expect to receive any metadata
+ * it has received and metadata files in successive
+ * chunks must be a strict superset of one another.
+ *
+ * Skip the first `metadata_sent` bytes to ensure
+ * they are not sent a second time to the client.
+ *
+ * Baring a block layer error or an internal error,
+ * this seek should not fail as
+ * `vstream->stream->metadata_received` is reset when
+ * a relay stream is rotated. If this is reached, it is
+ * safe to assume that
+ * `metadata_received` > `metadata_sent`.
+ */
+ const off_t seek_ret = fs_handle_seek(fs_handle,
+ vstream->metadata_sent, SEEK_SET);
+
+ if (seek_ret < 0) {
+ PERROR("Failed to seek metadata viewer stream file to `sent` position: pos = %" PRId64,
+ vstream->metadata_sent);
+ reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
+ goto send_reply;
+ }
+ }
+ }
+
+ reply.len = htobe64(len);
+ data = (char *) zmalloc(len);
+ if (!data) {
+ PERROR("viewer metadata zmalloc");
+ goto error;
+ }
+
+ fd = fs_handle_get_fd(vstream->stream_file.handle);
+ if (fd < 0) {
+ ERR("Failed to restore viewer stream file system handle");
+ goto error;
+ }
+ read_len = lttng_read(fd, data, len);
+ fs_handle_put_fd(vstream->stream_file.handle);
+ fd = -1;
+ if (read_len < len) {
+ if (read_len < 0) {
+ PERROR("Failed to read metadata file");
+ goto error;
+ } else {
+ /*
+ * A clear has been performed which prevents the relay
+ * from sending `len` bytes of metadata.
+ *
+ * It is important not to send any metadata if we
+ * couldn't read all the available metadata in one shot:
+ * sending partial metadata can cause the client to
+ * attempt to parse an incomplete (incoherent) metadata
+ * stream, which would result in an error.
+ */
+ const off_t seek_ret = fs_handle_seek(
+ vstream->stream_file.handle, -read_len,
+ SEEK_CUR);
+
+ DBG("Failed to read metadata: requested = %" PRIu64 ", got = %zd",
+ len, read_len);
+ read_len = 0;
+ len = 0;
+ if (seek_ret < 0) {
+ PERROR("Failed to restore metadata file position after partial read");
+ ret = -1;
+ goto error;
+ }
+ }
+ }
+ vstream->metadata_sent += read_len;
+ reply.status = htobe32(LTTNG_VIEWER_METADATA_OK);
+
+ goto send_reply;
+
+error:
+ reply.status = htobe32(LTTNG_VIEWER_METADATA_ERR);
+
+send_reply:
+ health_code_update();
+ if (vstream) {
+ pthread_mutex_unlock(&vstream->stream->lock);
+ }
+ ret = send_response(conn->sock, &reply, sizeof(reply));
+ if (ret < 0) {
+ goto end_free;
+ }
+ health_code_update();
+
+ if (len > 0) {
+ ret = send_response(conn->sock, data, len);
+ if (ret < 0) {
+ goto end_free;
+ }
+ }
+
+ DBG("Sent %" PRIu64 " bytes of metadata for stream %" PRIu64, len,
+ (uint64_t) be64toh(request.stream_id));
+
+ DBG("Metadata sent");
+
+end_free:
+ free(data);
+end:
+ if (vstream) {
+ viewer_stream_put(vstream);
+ }
+ return ret;
+}
+
+/*
+ * Create a viewer session.
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+int viewer_create_session(struct relay_connection *conn)
+{
+ int ret;
+ struct lttng_viewer_create_session_response resp;
+
+ DBG("Viewer create session received");
+
+ memset(&resp, 0, sizeof(resp));
+ resp.status = htobe32(LTTNG_VIEWER_CREATE_SESSION_OK);
+ conn->viewer_session = viewer_session_create();
+ if (!conn->viewer_session) {
+ ERR("Allocation viewer session");
+ resp.status = htobe32(LTTNG_VIEWER_CREATE_SESSION_ERR);
+ goto send_reply;
+ }
+
+send_reply:
+ health_code_update();
+ ret = send_response(conn->sock, &resp, sizeof(resp));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/*
+ * Detach a viewer session.
+ *
+ * Return 0 on success or else a negative value.
+ */
+static
+int viewer_detach_session(struct relay_connection *conn)
+{
+ int ret;
+ struct lttng_viewer_detach_session_response response;
+ struct lttng_viewer_detach_session_request request;
+ struct relay_session *session = NULL;
+ uint64_t viewer_session_to_close;
+
+ DBG("Viewer detach session received");
+
+ LTTNG_ASSERT(conn);
+
+ health_code_update();
+
+ /* Receive the request from the connected client. */
+ ret = recv_request(conn->sock, &request, sizeof(request));
+ if (ret < 0) {
+ goto end;
+ }
+ viewer_session_to_close = be64toh(request.session_id);
+
+ if (!conn->viewer_session) {
+ DBG("Client trying to detach before creating a live viewer session");
+ response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_ERR);
+ goto send_reply;
+ }
+
+ health_code_update();
+
+ memset(&response, 0, sizeof(response));
+ DBG("Detaching from session ID %" PRIu64, viewer_session_to_close);
+
+ session = session_get_by_id(be64toh(request.session_id));
+ if (!session) {
+ DBG("Relay session %" PRIu64 " not found",
+ (uint64_t) be64toh(request.session_id));
+ response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_UNK);
+ goto send_reply;
+ }
+
+ ret = viewer_session_is_attached(conn->viewer_session, session);
+ if (ret != 1) {
+ DBG("Not attached to this session");
+ response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_ERR);
+ goto send_reply_put;
+ }
+
+ viewer_session_close_one_session(conn->viewer_session, session);
+ response.status = htobe32(LTTNG_VIEWER_DETACH_SESSION_OK);
+ DBG("Session %" PRIu64 " detached.", viewer_session_to_close);
+
+send_reply_put:
+ session_put(session);
+
+send_reply:
+ health_code_update();
+ ret = send_response(conn->sock, &response, sizeof(response));
+ if (ret < 0) {
+ goto end;
+ }
+ health_code_update();
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/*
+ * live_relay_unknown_command: send -1 if received unknown command
+ */
+static
+void live_relay_unknown_command(struct relay_connection *conn)
+{
+ struct lttcomm_relayd_generic_reply reply;
+
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ (void) send_response(conn->sock, &reply, sizeof(reply));
+}
+
+/*
+ * Process the commands received on the control socket
+ */
+static
+int process_control(struct lttng_viewer_cmd *recv_hdr,
+ struct relay_connection *conn)
+{
+ int ret = 0;
+ uint32_t msg_value;
+
+ msg_value = be32toh(recv_hdr->cmd);
+
+ /*
+ * Make sure we've done the version check before any command other then a
+ * new client connection.
+ */
+ if (msg_value != LTTNG_VIEWER_CONNECT && !conn->version_check_done) {
+ ERR("Viewer conn value %" PRIu32 " before version check", msg_value);
+ ret = -1;
+ goto end;
+ }
+
+ switch (msg_value) {
+ case LTTNG_VIEWER_CONNECT:
+ ret = viewer_connect(conn);
+ break;
+ case LTTNG_VIEWER_LIST_SESSIONS:
+ ret = viewer_list_sessions(conn);
+ break;
+ case LTTNG_VIEWER_ATTACH_SESSION:
+ ret = viewer_attach_session(conn);
+ break;
+ case LTTNG_VIEWER_GET_NEXT_INDEX:
+ ret = viewer_get_next_index(conn);
+ break;
+ case LTTNG_VIEWER_GET_PACKET:
+ ret = viewer_get_packet(conn);
+ break;
+ case LTTNG_VIEWER_GET_METADATA:
+ ret = viewer_get_metadata(conn);
+ break;
+ case LTTNG_VIEWER_GET_NEW_STREAMS:
+ ret = viewer_get_new_streams(conn);
+ break;
+ case LTTNG_VIEWER_CREATE_SESSION:
+ ret = viewer_create_session(conn);
+ break;
+ case LTTNG_VIEWER_DETACH_SESSION:
+ ret = viewer_detach_session(conn);
+ break;
+ default:
+ ERR("Received unknown viewer command (%u)",
+ be32toh(recv_hdr->cmd));
+ live_relay_unknown_command(conn);
+ ret = -1;
+ goto end;
+ }
+
+end:
+ return ret;
+}
+
+static
+void cleanup_connection_pollfd(struct lttng_poll_event *events, int pollfd)
+{
+ int ret;
+
+ (void) lttng_poll_del(events, pollfd);
+
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &pollfd, 1,
+ fd_tracker_util_close_fd, NULL);
+ if (ret < 0) {
+ ERR("Closing pollfd %d", pollfd);
+ }
+}
+
+/*
+ * This thread does the actual work
+ */
+static
+void *thread_worker(void *data)
+{
+ int ret, err = -1;
+ uint32_t nb_fd;
+ struct lttng_poll_event events;
+ struct lttng_ht *viewer_connections_ht;
+ struct lttng_ht_iter iter;
+ struct lttng_viewer_cmd recv_hdr;
+ struct relay_connection *destroy_conn;
+
+ DBG("[thread] Live viewer relay worker started");
+
+ rcu_register_thread();
+
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_LIVE_WORKER);
+
+ if (testpoint(relayd_thread_live_worker)) {
+ goto error_testpoint;
+ }
+
+ /* table of connections indexed on socket */
+ viewer_connections_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG);
+ if (!viewer_connections_ht) {
+ goto viewer_connections_ht_error;
+ }
+
+ ret = create_named_thread_poll_set(&events, 2,
+ "Live viewer worker thread epoll");
+ if (ret < 0) {
+ goto error_poll_create;
+ }
+
+ ret = lttng_poll_add(&events, live_conn_pipe[0], LPOLLIN | LPOLLRDHUP);
+ if (ret < 0) {
+ goto error;
+ }
+
+restart:
+ while (1) {
+ int i;
+
+ health_code_update();
+
+ /* Infinite blocking call, waiting for transmission */
+ DBG3("Relayd live viewer worker thread polling...");
+ health_poll_entry();
+ ret = lttng_poll_wait(&events, -1);
+ health_poll_exit();
+ if (ret < 0) {
+ /*
+ * Restart interrupted system call.
+ */
+ if (errno == EINTR) {
+ goto restart;
+ }
+ goto error;
+ }
+
+ nb_fd = ret;
+
+ /*
+ * Process control. The control connection is prioritised so we don't
+ * starve it with high throughput tracing data on the data
+ * connection.
+ */
+ for (i = 0; i < nb_fd; i++) {
+ /* Fetch once the poll data */
+ uint32_t revents = LTTNG_POLL_GETEV(&events, i);
+ int pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ health_code_update();
+
+ /* Thread quit pipe has been closed. Killing thread. */
+ ret = check_thread_quit_pipe(pollfd, revents);
+ if (ret) {
+ err = 0;
+ goto exit;
+ }
+
+ /* Inspect the relay conn pipe for new connection. */
+ if (pollfd == live_conn_pipe[0]) {
+ if (revents & LPOLLIN) {
+ struct relay_connection *conn;
+
+ ret = lttng_read(live_conn_pipe[0],
+ &conn, sizeof(conn));
+ if (ret < 0) {
+ goto error;
+ }
+ ret = lttng_poll_add(&events,
+ conn->sock->fd,
+ LPOLLIN | LPOLLRDHUP);
+ if (ret) {
+ ERR("Failed to add new live connection file descriptor to poll set");
+ goto error;
+ }
+ connection_ht_add(viewer_connections_ht, conn);
+ DBG("Connection socket %d added to poll", conn->sock->fd);
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ ERR("Relay live pipe error");
+ goto error;
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ goto error;
+ }
+ } else {
+ /* Connection activity. */
+ struct relay_connection *conn;
+
+ conn = connection_get_by_sock(viewer_connections_ht, pollfd);
+ if (!conn) {
+ continue;
+ }
+
+ if (revents & LPOLLIN) {
+ ret = conn->sock->ops->recvmsg(conn->sock, &recv_hdr,
+ sizeof(recv_hdr), 0);
+ if (ret <= 0) {
+ /* Connection closed. */
+ cleanup_connection_pollfd(&events, pollfd);
+ /* Put "create" ownership reference. */
+ connection_put(conn);
+ DBG("Viewer control conn closed with %d", pollfd);
+ } else {
+ ret = process_control(&recv_hdr, conn);
+ if (ret < 0) {
+ /* Clear the session on error. */
+ cleanup_connection_pollfd(&events, pollfd);
+ /* Put "create" ownership reference. */
+ connection_put(conn);
+ DBG("Viewer connection closed with %d", pollfd);
+ }
+ }
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ cleanup_connection_pollfd(&events, pollfd);
+ /* Put "create" ownership reference. */
+ connection_put(conn);
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ connection_put(conn);
+ goto error;
+ }
+ /* Put local "get_by_sock" reference. */
+ connection_put(conn);
+ }
+ }
+ }
+
+exit:
+error:
+ (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
+
+ /* Cleanup remaining connection object. */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(viewer_connections_ht->ht, &iter.iter,
+ destroy_conn,
+ sock_n.node) {
+ health_code_update();
+ connection_put(destroy_conn);
+ }
+ rcu_read_unlock();
+error_poll_create:
+ lttng_ht_destroy(viewer_connections_ht);
+viewer_connections_ht_error:
+ /* Close relay conn pipes */
+ (void) fd_tracker_util_pipe_close(the_fd_tracker, live_conn_pipe);
+ if (err) {
+ DBG("Viewer worker thread exited with error");
+ }
+ DBG("Viewer worker thread cleanup complete");
+error_testpoint:
+ if (err) {
+ health_error();
+ ERR("Health error occurred in %s", __func__);
+ }
+ health_unregister(health_relayd);
+ if (lttng_relay_stop_threads()) {
+ ERR("Error stopping threads");
+ }
+ rcu_unregister_thread();
+ return NULL;
+}
+
+/*
+ * Create the relay command pipe to wake thread_manage_apps.
+ * Closed in cleanup().
+ */
+static int create_conn_pipe(void)
+{
+ return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
+ "Live connection pipe", live_conn_pipe);
+}
+
+int relayd_live_join(void)
+{
+ int ret, retval = 0;
+ void *status;
+
+ ret = pthread_join(live_listener_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join live listener");
+ retval = -1;
+ }
+
+ ret = pthread_join(live_worker_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join live worker");
+ retval = -1;
+ }
+
+ ret = pthread_join(live_dispatcher_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join live dispatcher");
+ retval = -1;
+ }
+
+ cleanup_relayd_live();
+
+ return retval;
+}
+
+/*
+ * main
+ */
+int relayd_live_create(struct lttng_uri *uri)
+{
+ int ret = 0, retval = 0;
+ void *status;
+ int is_root;
+
+ if (!uri) {
+ retval = -1;
+ goto exit_init_data;
+ }
+ live_uri = uri;
+
+ /* Check if daemon is UID = 0 */
+ is_root = !getuid();
+
+ if (!is_root) {
+ if (live_uri->port < 1024) {
+ ERR("Need to be root to use ports < 1024");
+ retval = -1;
+ goto exit_init_data;
+ }
+ }
+
+ /* Setup the thread apps communication pipe. */
+ if (create_conn_pipe()) {
+ retval = -1;
+ goto exit_init_data;
+ }
+
+ /* Init relay command queue. */
+ cds_wfcq_init(&viewer_conn_queue.head, &viewer_conn_queue.tail);
+
+ /* Set up max poll set size */
+ if (lttng_poll_set_max_size()) {
+ retval = -1;
+ goto exit_init_data;
+ }
+
+ /* Setup the dispatcher thread */
+ ret = pthread_create(&live_dispatcher_thread, default_pthread_attr(),
+ thread_dispatcher, (void *) NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create viewer dispatcher");
+ retval = -1;
+ goto exit_dispatcher_thread;
+ }
+
+ /* Setup the worker thread */
+ ret = pthread_create(&live_worker_thread, default_pthread_attr(),
+ thread_worker, NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create viewer worker");
+ retval = -1;
+ goto exit_worker_thread;
+ }
+
+ /* Setup the listener thread */
+ ret = pthread_create(&live_listener_thread, default_pthread_attr(),
+ thread_listener, (void *) NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create viewer listener");
+ retval = -1;
+ goto exit_listener_thread;
+ }
+
+ /*
+ * All OK, started all threads.
+ */
+ return retval;
+
+ /*
+ * Join on the live_listener_thread should anything be added after
+ * the live_listener thread's creation.
+ */
+
+exit_listener_thread:
+
+ ret = pthread_join(live_worker_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join live worker");
+ retval = -1;
+ }
+exit_worker_thread:
+
+ ret = pthread_join(live_dispatcher_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join live dispatcher");
+ retval = -1;
+ }
+exit_dispatcher_thread:
+
+exit_init_data:
+ cleanup_relayd_live();
+
+ return retval;
+}
+++ /dev/null
-/*
- * Copyright (C) 2012 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2012 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <getopt.h>
-#include <grp.h>
-#include <limits.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/mount.h>
-#include <sys/resource.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/resource.h>
-#include <inttypes.h>
-#include <urcu/futex.h>
-#include <urcu/uatomic.h>
-#include <urcu/rculist.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <strings.h>
-#include <ctype.h>
-
-#include <lttng/lttng.h>
-#include <common/common.h>
-#include <common/compat/poll.h>
-#include <common/compat/socket.h>
-#include <common/compat/endian.h>
-#include <common/compat/getenv.h>
-#include <common/defaults.h>
-#include <common/daemonize.h>
-#include <common/futex.h>
-#include <common/sessiond-comm/sessiond-comm.h>
-#include <common/sessiond-comm/inet.h>
-#include <common/sessiond-comm/relayd.h>
-#include <common/uri.h>
-#include <common/utils.h>
-#include <common/align.h>
-#include <common/config/session-config.h>
-#include <common/dynamic-buffer.h>
-#include <common/buffer-view.h>
-#include <common/string-utils/format.h>
-#include <common/fd-tracker/fd-tracker.h>
-#include <common/fd-tracker/utils.h>
-
-#include "backward-compatibility-group-by.h"
-#include "cmd.h"
-#include "connection.h"
-#include "ctf-trace.h"
-#include "health-relayd.h"
-#include "index.h"
-#include "live.h"
-#include "lttng-relayd.h"
-#include "session.h"
-#include "sessiond-trace-chunks.h"
-#include "stream.h"
-#include "tcp_keep_alive.h"
-#include "testpoint.h"
-#include "tracefile-array.h"
-#include "utils.h"
-#include "version.h"
-#include "viewer-stream.h"
-
-static const char *help_msg =
-#ifdef LTTNG_EMBED_HELP
-#include <lttng-relayd.8.h>
-#else
-NULL
-#endif
-;
-
-enum relay_connection_status {
- RELAY_CONNECTION_STATUS_OK,
- /* An error occurred while processing an event on the connection. */
- RELAY_CONNECTION_STATUS_ERROR,
- /* Connection closed/shutdown cleanly. */
- RELAY_CONNECTION_STATUS_CLOSED,
-};
-
-/* command line options */
-char *opt_output_path, *opt_working_directory;
-static int opt_daemon, opt_background, opt_print_version, opt_allow_clear = 1;
-enum relay_group_output_by opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_UNKNOWN;
-
-/*
- * We need to wait for listener and live listener threads, as well as
- * health check thread, before being ready to signal readiness.
- */
-#define NR_LTTNG_RELAY_READY 3
-static int lttng_relay_ready = NR_LTTNG_RELAY_READY;
-
-/* Size of receive buffer. */
-#define RECV_DATA_BUFFER_SIZE 65536
-
-static int recv_child_signal; /* Set to 1 when a SIGUSR1 signal is received. */
-static pid_t child_ppid; /* Internal parent PID use with daemonize. */
-
-static struct lttng_uri *control_uri;
-static struct lttng_uri *data_uri;
-static struct lttng_uri *live_uri;
-
-const char *progname;
-
-const char *tracing_group_name = DEFAULT_TRACING_GROUP;
-static int tracing_group_name_override;
-
-const char * const config_section_name = "relayd";
-
-/*
- * Quit pipe for all threads. This permits a single cancellation point
- * for all threads when receiving an event on the pipe.
- */
-int thread_quit_pipe[2] = { -1, -1 };
-
-/*
- * This pipe is used to inform the worker thread that a command is queued and
- * ready to be processed.
- */
-static int relay_conn_pipe[2] = { -1, -1 };
-
-/* Shared between threads */
-static int dispatch_thread_exit;
-
-static pthread_t listener_thread;
-static pthread_t dispatcher_thread;
-static pthread_t worker_thread;
-static pthread_t health_thread;
-
-/*
- * last_relay_stream_id_lock protects last_relay_stream_id increment
- * atomicity on 32-bit architectures.
- */
-static pthread_mutex_t last_relay_stream_id_lock = PTHREAD_MUTEX_INITIALIZER;
-static uint64_t last_relay_stream_id;
-
-/*
- * Relay command queue.
- *
- * The relay_thread_listener and relay_thread_dispatcher communicate with this
- * queue.
- */
-static struct relay_conn_queue relay_conn_queue;
-
-/* Cap of file desriptors to be in simultaneous use by the relay daemon. */
-static unsigned int lttng_opt_fd_pool_size = -1;
-
-/* Global relay stream hash table. */
-struct lttng_ht *relay_streams_ht;
-
-/* Global relay viewer stream hash table. */
-struct lttng_ht *viewer_streams_ht;
-
-/* Global relay sessions hash table. */
-struct lttng_ht *sessions_ht;
-
-/* Relayd health monitoring */
-struct health_app *health_relayd;
-
-struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry;
-
-/* Global fd tracker. */
-struct fd_tracker *the_fd_tracker;
-
-static struct option long_options[] = {
- { "control-port", 1, 0, 'C', },
- { "data-port", 1, 0, 'D', },
- { "live-port", 1, 0, 'L', },
- { "daemonize", 0, 0, 'd', },
- { "background", 0, 0, 'b', },
- { "group", 1, 0, 'g', },
- { "fd-pool-size", 1, 0, '\0', },
- { "help", 0, 0, 'h', },
- { "output", 1, 0, 'o', },
- { "verbose", 0, 0, 'v', },
- { "config", 1, 0, 'f' },
- { "version", 0, 0, 'V' },
- { "working-directory", 1, 0, 'w', },
- { "group-output-by-session", 0, 0, 's', },
- { "group-output-by-host", 0, 0, 'p', },
- { "disallow-clear", 0, 0, 'x' },
- { NULL, 0, 0, 0, },
-};
-
-static const char *config_ignore_options[] = { "help", "config", "version" };
-
-static void print_version(void) {
- fprintf(stdout, "%s\n", VERSION);
-}
-
-static void relayd_config_log(void)
-{
- DBG("LTTng-relayd " VERSION " - " VERSION_NAME "%s%s",
- GIT_VERSION[0] == '\0' ? "" : " - " GIT_VERSION,
- EXTRA_VERSION_NAME[0] == '\0' ? "" : " - " EXTRA_VERSION_NAME);
- if (EXTRA_VERSION_DESCRIPTION[0] != '\0') {
- DBG("LTTng-relayd extra version description:\n\t" EXTRA_VERSION_DESCRIPTION "\n");
- }
- if (EXTRA_VERSION_PATCHES[0] != '\0') {
- DBG("LTTng-relayd extra patches:\n\t" EXTRA_VERSION_PATCHES "\n");
- }
-}
-
-/*
- * Take an option from the getopt output and set it in the right variable to be
- * used later.
- *
- * Return 0 on success else a negative value.
- */
-static int set_option(int opt, const char *arg, const char *optname)
-{
- int ret;
-
- switch (opt) {
- case 0:
- if (!strcmp(optname, "fd-pool-size")) {
- unsigned long v;
-
- errno = 0;
- v = strtoul(arg, NULL, 0);
- if (errno != 0 || !isdigit((unsigned char) arg[0])) {
- ERR("Wrong value in --fd-pool-size parameter: %s", arg);
- ret = -1;
- goto end;
- }
- if (v >= UINT_MAX) {
- ERR("File descriptor cap overflow in --fd-pool-size parameter: %s", arg);
- ret = -1;
- goto end;
- }
- lttng_opt_fd_pool_size = (unsigned int) v;
- } else {
- fprintf(stderr, "unknown option %s", optname);
- if (arg) {
- fprintf(stderr, " with arg %s\n", arg);
- }
- }
- break;
- case 'C':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-C, --control-port");
- } else {
- ret = uri_parse(arg, &control_uri);
- if (ret < 0) {
- ERR("Invalid control URI specified");
- goto end;
- }
- if (control_uri->port == 0) {
- control_uri->port = DEFAULT_NETWORK_CONTROL_PORT;
- }
- }
- break;
- case 'D':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-D, -data-port");
- } else {
- ret = uri_parse(arg, &data_uri);
- if (ret < 0) {
- ERR("Invalid data URI specified");
- goto end;
- }
- if (data_uri->port == 0) {
- data_uri->port = DEFAULT_NETWORK_DATA_PORT;
- }
- }
- break;
- case 'L':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-L, -live-port");
- } else {
- ret = uri_parse(arg, &live_uri);
- if (ret < 0) {
- ERR("Invalid live URI specified");
- goto end;
- }
- if (live_uri->port == 0) {
- live_uri->port = DEFAULT_NETWORK_VIEWER_PORT;
- }
- }
- break;
- case 'd':
- opt_daemon = 1;
- break;
- case 'b':
- opt_background = 1;
- break;
- case 'g':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-g, --group");
- } else {
- tracing_group_name = strdup(arg);
- if (tracing_group_name == NULL) {
- ret = -errno;
- PERROR("strdup");
- goto end;
- }
- tracing_group_name_override = 1;
- }
- break;
- case 'h':
- ret = utils_show_help(8, "lttng-relayd", help_msg);
- if (ret) {
- ERR("Cannot show --help for `lttng-relayd`");
- perror("exec");
- }
- exit(EXIT_FAILURE);
- case 'V':
- opt_print_version = 1;
- break;
- case 'o':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-o, --output");
- } else {
- ret = asprintf(&opt_output_path, "%s", arg);
- if (ret < 0) {
- ret = -errno;
- PERROR("asprintf opt_output_path");
- goto end;
- }
- }
- break;
- case 'w':
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-w, --working-directory");
- } else {
- ret = asprintf(&opt_working_directory, "%s", arg);
- if (ret < 0) {
- ret = -errno;
- PERROR("asprintf opt_working_directory");
- goto end;
- }
- }
- break;
-
- case 'v':
- /* Verbose level can increase using multiple -v */
- if (arg) {
- lttng_opt_verbose = config_parse_value(arg);
- } else {
- /* Only 3 level of verbosity (-vvv). */
- if (lttng_opt_verbose < 3) {
- lttng_opt_verbose += 1;
- }
- }
- break;
- case 's':
- if (opt_group_output_by != RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
- ERR("Cannot set --group-output-by-session, another --group-output-by argument is present");
- exit(EXIT_FAILURE);
- }
- opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_SESSION;
- break;
- case 'p':
- if (opt_group_output_by != RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
- ERR("Cannot set --group-output-by-host, another --group-output-by argument is present");
- exit(EXIT_FAILURE);
- }
- opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_HOST;
- break;
- case 'x':
- /* Disallow clear */
- opt_allow_clear = 0;
- break;
- default:
- /* Unknown option or other error.
- * Error is printed by getopt, just return */
- ret = -1;
- goto end;
- }
-
- /* All good. */
- ret = 0;
-
-end:
- return ret;
-}
-
-/*
- * config_entry_handler_cb used to handle options read from a config file.
- * See config_entry_handler_cb comment in common/config/session-config.h for the
- * return value conventions.
- */
-static int config_entry_handler(const struct config_entry *entry, void *unused)
-{
- int ret = 0, i;
-
- if (!entry || !entry->name || !entry->value) {
- ret = -EINVAL;
- goto end;
- }
-
- /* Check if the option is to be ignored */
- for (i = 0; i < sizeof(config_ignore_options) / sizeof(char *); i++) {
- if (!strcmp(entry->name, config_ignore_options[i])) {
- goto end;
- }
- }
-
- for (i = 0; i < (sizeof(long_options) / sizeof(struct option)) - 1; i++) {
- /* Ignore if entry name is not fully matched. */
- if (strcmp(entry->name, long_options[i].name)) {
- continue;
- }
-
- /*
- * If the option takes no argument on the command line,
- * we have to check if the value is "true". We support
- * non-zero numeric values, true, on and yes.
- */
- if (!long_options[i].has_arg) {
- ret = config_parse_value(entry->value);
- if (ret <= 0) {
- if (ret) {
- WARN("Invalid configuration value \"%s\" for option %s",
- entry->value, entry->name);
- }
- /* False, skip boolean config option. */
- goto end;
- }
- }
-
- ret = set_option(long_options[i].val, entry->value, entry->name);
- goto end;
- }
-
- WARN("Unrecognized option \"%s\" in daemon configuration file.",
- entry->name);
-
-end:
- return ret;
-}
-
-static int parse_env_options(void)
-{
- int ret = 0;
- char *value = NULL;
-
- value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_WORKING_DIRECTORY_ENV);
- if (value) {
- opt_working_directory = strdup(value);
- if (!opt_working_directory) {
- ERR("Failed to allocate working directory string (\"%s\")",
- value);
- ret = -1;
- }
- }
- return ret;
-}
-
-static int set_fd_pool_size(void)
-{
- int ret = 0;
- struct rlimit rlimit;
-
- ret = getrlimit(RLIMIT_NOFILE, &rlimit);
- if (ret) {
- PERROR("Failed to get file descriptor limit");
- ret = -1;
- goto end;
- }
-
- DBG("File descriptor count limits are %" PRIu64 " (soft) and %" PRIu64 " (hard)",
- (uint64_t) rlimit.rlim_cur,
- (uint64_t) rlimit.rlim_max);
- if (lttng_opt_fd_pool_size == -1) {
- /* Use default value (soft limit - reserve). */
- if (rlimit.rlim_cur < DEFAULT_RELAYD_MIN_FD_POOL_SIZE) {
- ERR("The process' file number limit is too low (%" PRIu64 "). The process' file number limit must be set to at least %i.",
- (uint64_t) rlimit.rlim_cur, DEFAULT_RELAYD_MIN_FD_POOL_SIZE);
- ret = -1;
- goto end;
- }
- lttng_opt_fd_pool_size = rlimit.rlim_cur -
- DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE;
- goto end;
- }
-
- if (lttng_opt_fd_pool_size < DEFAULT_RELAYD_MIN_FD_POOL_SIZE) {
- ERR("File descriptor pool size must be set to at least %d",
- DEFAULT_RELAYD_MIN_FD_POOL_SIZE);
- ret = -1;
- goto end;
- }
-
- if (lttng_opt_fd_pool_size > rlimit.rlim_cur) {
- ERR("File descriptor pool size argument (%u) exceeds the process' soft limit (%" PRIu64 ").",
- lttng_opt_fd_pool_size, (uint64_t) rlimit.rlim_cur);
- ret = -1;
- goto end;
- }
-
- DBG("File descriptor pool size argument (%u) adjusted to %u to accommodates transient fd uses",
- lttng_opt_fd_pool_size,
- lttng_opt_fd_pool_size - DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE);
- lttng_opt_fd_pool_size -= DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE;
-end:
- return ret;
-}
-
-static int set_options(int argc, char **argv)
-{
- int c, ret = 0, option_index = 0, retval = 0;
- int orig_optopt = optopt, orig_optind = optind;
- char *default_address, *optstring;
- char *config_path = NULL;
-
- optstring = utils_generate_optstring(long_options,
- sizeof(long_options) / sizeof(struct option));
- if (!optstring) {
- retval = -ENOMEM;
- goto exit;
- }
-
- /* Check for the --config option */
-
- while ((c = getopt_long(argc, argv, optstring, long_options,
- &option_index)) != -1) {
- if (c == '?') {
- retval = -EINVAL;
- goto exit;
- } else if (c != 'f') {
- continue;
- }
-
- if (lttng_is_setuid_setgid()) {
- WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
- "-f, --config");
- } else {
- free(config_path);
- config_path = utils_expand_path(optarg);
- if (!config_path) {
- ERR("Failed to resolve path: %s", optarg);
- }
- }
- }
-
- ret = config_get_section_entries(config_path, config_section_name,
- config_entry_handler, NULL);
- if (ret) {
- if (ret > 0) {
- ERR("Invalid configuration option at line %i", ret);
- }
- retval = -1;
- goto exit;
- }
-
- /* Reset getopt's global state */
- optopt = orig_optopt;
- optind = orig_optind;
- while (1) {
- c = getopt_long(argc, argv, optstring, long_options, &option_index);
- if (c == -1) {
- break;
- }
-
- ret = set_option(c, optarg, long_options[option_index].name);
- if (ret < 0) {
- retval = -1;
- goto exit;
- }
- }
-
- /* assign default values */
- if (control_uri == NULL) {
- ret = asprintf(&default_address,
- "tcp://" DEFAULT_NETWORK_CONTROL_BIND_ADDRESS ":%d",
- DEFAULT_NETWORK_CONTROL_PORT);
- if (ret < 0) {
- PERROR("asprintf default data address");
- retval = -1;
- goto exit;
- }
-
- ret = uri_parse(default_address, &control_uri);
- free(default_address);
- if (ret < 0) {
- ERR("Invalid control URI specified");
- retval = -1;
- goto exit;
- }
- }
- if (data_uri == NULL) {
- ret = asprintf(&default_address,
- "tcp://" DEFAULT_NETWORK_DATA_BIND_ADDRESS ":%d",
- DEFAULT_NETWORK_DATA_PORT);
- if (ret < 0) {
- PERROR("asprintf default data address");
- retval = -1;
- goto exit;
- }
-
- ret = uri_parse(default_address, &data_uri);
- free(default_address);
- if (ret < 0) {
- ERR("Invalid data URI specified");
- retval = -1;
- goto exit;
- }
- }
- if (live_uri == NULL) {
- ret = asprintf(&default_address,
- "tcp://" DEFAULT_NETWORK_VIEWER_BIND_ADDRESS ":%d",
- DEFAULT_NETWORK_VIEWER_PORT);
- if (ret < 0) {
- PERROR("asprintf default viewer control address");
- retval = -1;
- goto exit;
- }
-
- ret = uri_parse(default_address, &live_uri);
- free(default_address);
- if (ret < 0) {
- ERR("Invalid viewer control URI specified");
- retval = -1;
- goto exit;
- }
- }
- ret = set_fd_pool_size();
- if (ret) {
- retval = -1;
- goto exit;
- }
-
- if (opt_group_output_by == RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
- opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_HOST;
- }
- if (opt_allow_clear) {
- /* Check if env variable exists. */
- const char *value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_DISALLOW_CLEAR_ENV);
- if (value) {
- ret = config_parse_value(value);
- if (ret < 0) {
- ERR("Invalid value for %s specified", DEFAULT_LTTNG_RELAYD_DISALLOW_CLEAR_ENV);
- retval = -1;
- goto exit;
- }
- opt_allow_clear = !ret;
- }
- }
-
-exit:
- free(config_path);
- free(optstring);
- return retval;
-}
-
-static void print_global_objects(void)
-{
- print_viewer_streams();
- print_relay_streams();
- print_sessions();
-}
-
-static int noop_close(void *data, int *fds)
-{
- return 0;
-}
-
-static void untrack_stdio(void)
-{
- int fds[] = { fileno(stdout), fileno(stderr) };
-
- /*
- * noop_close is used since we don't really want to close
- * the stdio output fds; we merely want to stop tracking them.
- */
- (void) fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- fds, 2, noop_close, NULL);
-}
-
-/*
- * Cleanup the daemon
- */
-static void relayd_cleanup(void)
-{
- print_global_objects();
-
- DBG("Cleaning up");
-
- if (viewer_streams_ht)
- lttng_ht_destroy(viewer_streams_ht);
- if (relay_streams_ht)
- lttng_ht_destroy(relay_streams_ht);
- if (sessions_ht)
- lttng_ht_destroy(sessions_ht);
-
- free(opt_output_path);
- free(opt_working_directory);
-
- if (health_relayd) {
- health_app_destroy(health_relayd);
- }
- /* Close thread quit pipes */
- if (health_quit_pipe[0] != -1) {
- (void) fd_tracker_util_pipe_close(
- the_fd_tracker, health_quit_pipe);
- }
- if (thread_quit_pipe[0] != -1) {
- (void) fd_tracker_util_pipe_close(
- the_fd_tracker, thread_quit_pipe);
- }
- if (sessiond_trace_chunk_registry) {
- sessiond_trace_chunk_registry_destroy(
- sessiond_trace_chunk_registry);
- }
- if (the_fd_tracker) {
- untrack_stdio();
- /*
- * fd_tracker_destroy() will log the contents of the fd-tracker
- * if a leak is detected.
- */
- fd_tracker_destroy(the_fd_tracker);
- }
-
- uri_free(control_uri);
- uri_free(data_uri);
- /* Live URI is freed in the live thread. */
-
- if (tracing_group_name_override) {
- free((void *) tracing_group_name);
- }
-}
-
-/*
- * Write to writable pipe used to notify a thread.
- */
-static int notify_thread_pipe(int wpipe)
-{
- ssize_t ret;
-
- ret = lttng_write(wpipe, "!", 1);
- if (ret < 1) {
- PERROR("write poll pipe");
- goto end;
- }
- ret = 0;
-end:
- return ret;
-}
-
-static int notify_health_quit_pipe(int *pipe)
-{
- ssize_t ret;
-
- ret = lttng_write(pipe[1], "4", 1);
- if (ret < 1) {
- PERROR("write relay health quit");
- goto end;
- }
- ret = 0;
-end:
- return ret;
-}
-
-/*
- * Stop all relayd and relayd-live threads.
- */
-int lttng_relay_stop_threads(void)
-{
- int retval = 0;
-
- /* Stopping all threads */
- DBG("Terminating all threads");
- if (notify_thread_pipe(thread_quit_pipe[1])) {
- ERR("write error on thread quit pipe");
- retval = -1;
- }
-
- if (notify_health_quit_pipe(health_quit_pipe)) {
- ERR("write error on health quit pipe");
- }
-
- /* Dispatch thread */
- CMM_STORE_SHARED(dispatch_thread_exit, 1);
- futex_nto1_wake(&relay_conn_queue.futex);
-
- if (relayd_live_stop()) {
- ERR("Error stopping live threads");
- retval = -1;
- }
- return retval;
-}
-
-/*
- * Signal handler for the daemon
- *
- * Simply stop all worker threads, leaving main() return gracefully after
- * joining all threads and calling cleanup().
- */
-static void sighandler(int sig)
-{
- switch (sig) {
- case SIGINT:
- DBG("SIGINT caught");
- if (lttng_relay_stop_threads()) {
- ERR("Error stopping threads");
- }
- break;
- case SIGTERM:
- DBG("SIGTERM caught");
- if (lttng_relay_stop_threads()) {
- ERR("Error stopping threads");
- }
- break;
- case SIGUSR1:
- CMM_STORE_SHARED(recv_child_signal, 1);
- break;
- default:
- break;
- }
-}
-
-/*
- * Setup signal handler for :
- * SIGINT, SIGTERM, SIGPIPE
- */
-static int set_signal_handler(void)
-{
- int ret = 0;
- struct sigaction sa;
- sigset_t sigset;
-
- if ((ret = sigemptyset(&sigset)) < 0) {
- PERROR("sigemptyset");
- return ret;
- }
-
- sa.sa_mask = sigset;
- sa.sa_flags = 0;
-
- sa.sa_handler = sighandler;
- if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) {
- PERROR("sigaction");
- return ret;
- }
-
- if ((ret = sigaction(SIGINT, &sa, NULL)) < 0) {
- PERROR("sigaction");
- return ret;
- }
-
- if ((ret = sigaction(SIGUSR1, &sa, NULL)) < 0) {
- PERROR("sigaction");
- return ret;
- }
-
- sa.sa_handler = SIG_IGN;
- if ((ret = sigaction(SIGPIPE, &sa, NULL)) < 0) {
- PERROR("sigaction");
- return ret;
- }
-
- DBG("Signal handler set for SIGTERM, SIGUSR1, SIGPIPE and SIGINT");
-
- return ret;
-}
-
-void lttng_relay_notify_ready(void)
-{
- /* Notify the parent of the fork() process that we are ready. */
- if (opt_daemon || opt_background) {
- if (uatomic_sub_return(<tng_relay_ready, 1) == 0) {
- kill(child_ppid, SIGUSR1);
- }
- }
-}
-
-/*
- * Init thread quit pipe.
- *
- * Return -1 on error or 0 if all pipes are created.
- */
-static int init_thread_quit_pipe(void)
-{
- return fd_tracker_util_pipe_open_cloexec(
- the_fd_tracker, "Quit pipe", thread_quit_pipe);
-}
-
-/*
- * Init health quit pipe.
- *
- * Return -1 on error or 0 if all pipes are created.
- */
-static int init_health_quit_pipe(void)
-{
- return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
- "Health quit pipe", health_quit_pipe);
-}
-
-/*
- * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set.
- */
-static int create_named_thread_poll_set(struct lttng_poll_event *events,
- int size, const char *name)
-{
- int ret;
-
- if (events == NULL || size == 0) {
- ret = -1;
- goto error;
- }
-
- ret = fd_tracker_util_poll_create(the_fd_tracker,
- name, events, 1, LTTNG_CLOEXEC);
- if (ret) {
- PERROR("Failed to create \"%s\" poll file descriptor", name);
- goto error;
- }
-
- /* Add quit pipe */
- ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR);
- if (ret < 0) {
- goto error;
- }
-
- return 0;
-
-error:
- return ret;
-}
-
-/*
- * Check if the thread quit pipe was triggered.
- *
- * Return 1 if it was triggered else 0;
- */
-static int check_thread_quit_pipe(int fd, uint32_t events)
-{
- if (fd == thread_quit_pipe[0] && (events & LPOLLIN)) {
- return 1;
- }
-
- return 0;
-}
-
-static int create_sock(void *data, int *out_fd)
-{
- int ret;
- struct lttcomm_sock *sock = data;
-
- ret = lttcomm_create_sock(sock);
- if (ret < 0) {
- goto end;
- }
-
- *out_fd = sock->fd;
-end:
- return ret;
-}
-
-static int close_sock(void *data, int *in_fd)
-{
- struct lttcomm_sock *sock = data;
-
- return sock->ops->close(sock);
-}
-
-static int accept_sock(void *data, int *out_fd)
-{
- int ret = 0;
- /* Socks is an array of in_sock, out_sock. */
- struct lttcomm_sock **socks = data;
- struct lttcomm_sock *in_sock = socks[0];
-
- socks[1] = in_sock->ops->accept(in_sock);
- if (!socks[1]) {
- ret = -1;
- goto end;
- }
- *out_fd = socks[1]->fd;
-end:
- return ret;
-}
-
-/*
- * Create and init socket from uri.
- */
-static struct lttcomm_sock *relay_socket_create(struct lttng_uri *uri,
- const char *name)
-{
- int ret, sock_fd;
- struct lttcomm_sock *sock = NULL;
- char uri_str[PATH_MAX];
- char *formated_name = NULL;
-
- sock = lttcomm_alloc_sock_from_uri(uri);
- if (sock == NULL) {
- ERR("Allocating socket");
- goto error;
- }
-
- /*
- * Don't fail to create the socket if the name can't be built as it is
- * only used for debugging purposes.
- */
- ret = uri_to_str_url(uri, uri_str, sizeof(uri_str));
- uri_str[sizeof(uri_str) - 1] = '\0';
- if (ret >= 0) {
- ret = asprintf(&formated_name, "%s socket @ %s", name,
- uri_str);
- if (ret < 0) {
- formated_name = NULL;
- }
- }
-
- ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd,
- (const char **) (formated_name ? &formated_name : NULL),
- 1, create_sock, sock);
- if (ret) {
- PERROR("Failed to open \"%s\" relay socket",
- formated_name ?: "Unknown");
- goto error;
- }
- DBG("Listening on %s socket %d", name, sock->fd);
-
- ret = sock->ops->bind(sock);
- if (ret < 0) {
- PERROR("Failed to bind socket");
- goto error;
- }
-
- ret = sock->ops->listen(sock, -1);
- if (ret < 0) {
- goto error;
-
- }
-
- free(formated_name);
- return sock;
-
-error:
- if (sock) {
- lttcomm_destroy_sock(sock);
- }
- free(formated_name);
- return NULL;
-}
-
-static
-struct lttcomm_sock *accept_relayd_sock(struct lttcomm_sock *listening_sock,
- const char *name)
-{
- int out_fd, ret;
- struct lttcomm_sock *socks[2] = { listening_sock, NULL };
- struct lttcomm_sock *new_sock = NULL;
-
- ret = fd_tracker_open_unsuspendable_fd(
- the_fd_tracker, &out_fd,
- (const char **) &name,
- 1, accept_sock, &socks);
- if (ret) {
- goto end;
- }
- new_sock = socks[1];
- DBG("%s accepted, socket %d", name, new_sock->fd);
-end:
- return new_sock;
-}
-
-/*
- * This thread manages the listening for new connections on the network
- */
-static void *relay_thread_listener(void *data)
-{
- int i, ret, pollfd, err = -1;
- uint32_t revents, nb_fd;
- struct lttng_poll_event events;
- struct lttcomm_sock *control_sock, *data_sock;
-
- DBG("[thread] Relay listener started");
-
- rcu_register_thread();
- health_register(health_relayd, HEALTH_RELAYD_TYPE_LISTENER);
-
- health_code_update();
-
- control_sock = relay_socket_create(control_uri, "Control listener");
- if (!control_sock) {
- goto error_sock_control;
- }
-
- data_sock = relay_socket_create(data_uri, "Data listener");
- if (!data_sock) {
- goto error_sock_relay;
- }
-
- /*
- * Pass 3 as size here for the thread quit pipe, control and
- * data socket.
- */
- ret = create_named_thread_poll_set(&events, 3, "Listener thread epoll");
- if (ret < 0) {
- goto error_create_poll;
- }
-
- /* Add the control socket */
- ret = lttng_poll_add(&events, control_sock->fd, LPOLLIN | LPOLLRDHUP);
- if (ret < 0) {
- goto error_poll_add;
- }
-
- /* Add the data socket */
- ret = lttng_poll_add(&events, data_sock->fd, LPOLLIN | LPOLLRDHUP);
- if (ret < 0) {
- goto error_poll_add;
- }
-
- lttng_relay_notify_ready();
-
- if (testpoint(relayd_thread_listener)) {
- goto error_testpoint;
- }
-
- while (1) {
- health_code_update();
-
- DBG("Listener accepting connections");
-
-restart:
- health_poll_entry();
- ret = lttng_poll_wait(&events, -1);
- health_poll_exit();
- if (ret < 0) {
- /*
- * Restart interrupted system call.
- */
- if (errno == EINTR) {
- goto restart;
- }
- goto error;
- }
-
- nb_fd = ret;
-
- DBG("Relay new connection received");
- for (i = 0; i < nb_fd; i++) {
- health_code_update();
-
- /* Fetch once the poll data */
- revents = LTTNG_POLL_GETEV(&events, i);
- pollfd = LTTNG_POLL_GETFD(&events, i);
-
- /* Thread quit pipe has been closed. Killing thread. */
- ret = check_thread_quit_pipe(pollfd, revents);
- if (ret) {
- err = 0;
- goto exit;
- }
-
- if (revents & LPOLLIN) {
- /*
- * A new connection is requested, therefore a
- * sessiond/consumerd connection is allocated in
- * this thread, enqueued to a global queue and
- * dequeued (and freed) in the worker thread.
- */
- int val = 1;
- struct relay_connection *new_conn;
- struct lttcomm_sock *newsock = NULL;
- enum connection_type type;
-
- if (pollfd == data_sock->fd) {
- type = RELAY_DATA;
- newsock = accept_relayd_sock(data_sock,
- "Data socket to relayd");
- } else {
- LTTNG_ASSERT(pollfd == control_sock->fd);
- type = RELAY_CONTROL;
- newsock = accept_relayd_sock(control_sock,
- "Control socket to relayd");
- }
- if (!newsock) {
- PERROR("accepting sock");
- goto error;
- }
-
- ret = setsockopt(newsock->fd, SOL_SOCKET, SO_REUSEADDR, &val,
- sizeof(val));
- if (ret < 0) {
- PERROR("setsockopt inet");
- lttcomm_destroy_sock(newsock);
- goto error;
- }
-
- ret = socket_apply_keep_alive_config(newsock->fd);
- if (ret < 0) {
- ERR("Failed to apply TCP keep-alive configuration on socket (%i)",
- newsock->fd);
- lttcomm_destroy_sock(newsock);
- goto error;
- }
-
- new_conn = connection_create(newsock, type);
- if (!new_conn) {
- lttcomm_destroy_sock(newsock);
- goto error;
- }
-
- /* Enqueue request for the dispatcher thread. */
- cds_wfcq_enqueue(&relay_conn_queue.head, &relay_conn_queue.tail,
- &new_conn->qnode);
-
- /*
- * Wake the dispatch queue futex.
- * Implicit memory barrier with the
- * exchange in cds_wfcq_enqueue.
- */
- futex_nto1_wake(&relay_conn_queue.futex);
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- ERR("socket poll error");
- goto error;
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- goto error;
- }
- }
- }
-
-exit:
-error:
-error_poll_add:
-error_testpoint:
- (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
-error_create_poll:
- if (data_sock->fd >= 0) {
- int data_sock_fd = data_sock->fd;
-
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- &data_sock_fd, 1, close_sock,
- data_sock);
- if (ret) {
- PERROR("Failed to close the data listener socket file descriptor");
- }
- data_sock->fd = -1;
- }
- lttcomm_destroy_sock(data_sock);
-error_sock_relay:
- if (control_sock->fd >= 0) {
- int control_sock_fd = control_sock->fd;
-
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
- &control_sock_fd, 1, close_sock,
- control_sock);
- if (ret) {
- PERROR("Failed to close the control listener socket file descriptor");
- }
- control_sock->fd = -1;
- }
- lttcomm_destroy_sock(control_sock);
-error_sock_control:
- if (err) {
- health_error();
- ERR("Health error occurred in %s", __func__);
- }
- health_unregister(health_relayd);
- rcu_unregister_thread();
- DBG("Relay listener thread cleanup complete");
- lttng_relay_stop_threads();
- return NULL;
-}
-
-/*
- * This thread manages the dispatching of the requests to worker threads
- */
-static void *relay_thread_dispatcher(void *data)
-{
- int err = -1;
- ssize_t ret;
- struct cds_wfcq_node *node;
- struct relay_connection *new_conn = NULL;
-
- DBG("[thread] Relay dispatcher started");
-
- health_register(health_relayd, HEALTH_RELAYD_TYPE_DISPATCHER);
-
- if (testpoint(relayd_thread_dispatcher)) {
- goto error_testpoint;
- }
-
- health_code_update();
-
- for (;;) {
- health_code_update();
-
- /* Atomically prepare the queue futex */
- futex_nto1_prepare(&relay_conn_queue.futex);
-
- if (CMM_LOAD_SHARED(dispatch_thread_exit)) {
- break;
- }
-
- do {
- health_code_update();
-
- /* Dequeue commands */
- node = cds_wfcq_dequeue_blocking(&relay_conn_queue.head,
- &relay_conn_queue.tail);
- if (node == NULL) {
- DBG("Woken up but nothing in the relay command queue");
- /* Continue thread execution */
- break;
- }
- new_conn = caa_container_of(node, struct relay_connection, qnode);
-
- DBG("Dispatching request waiting on sock %d", new_conn->sock->fd);
-
- /*
- * Inform worker thread of the new request. This
- * call is blocking so we can be assured that
- * the data will be read at some point in time
- * or wait to the end of the world :)
- */
- ret = lttng_write(relay_conn_pipe[1], &new_conn, sizeof(new_conn));
- if (ret < 0) {
- PERROR("write connection pipe");
- connection_put(new_conn);
- goto error;
- }
- } while (node != NULL);
-
- /* Futex wait on queue. Blocking call on futex() */
- health_poll_entry();
- futex_nto1_wait(&relay_conn_queue.futex);
- health_poll_exit();
- }
-
- /* Normal exit, no error */
- err = 0;
-
-error:
-error_testpoint:
- if (err) {
- health_error();
- ERR("Health error occurred in %s", __func__);
- }
- health_unregister(health_relayd);
- DBG("Dispatch thread dying");
- lttng_relay_stop_threads();
- return NULL;
-}
-
-static bool session_streams_have_index(const struct relay_session *session)
-{
- return session->minor >= 4 && !session->snapshot;
-}
-
-/*
- * Handle the RELAYD_CREATE_SESSION command.
- *
- * On success, send back the session id or else return a negative value.
- */
-static int relay_create_session(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- ssize_t send_ret;
- struct relay_session *session = NULL;
- struct lttcomm_relayd_create_session_reply_2_11 reply = {};
- char session_name[LTTNG_NAME_MAX] = {};
- char hostname[LTTNG_HOST_NAME_MAX] = {};
- uint32_t live_timer = 0;
- bool snapshot = false;
- bool session_name_contains_creation_timestamp = false;
- /* Left nil for peers < 2.11. */
- char base_path[LTTNG_PATH_MAX] = {};
- lttng_uuid sessiond_uuid = {};
- LTTNG_OPTIONAL(uint64_t) id_sessiond = {};
- LTTNG_OPTIONAL(uint64_t) current_chunk_id = {};
- LTTNG_OPTIONAL(time_t) creation_time = {};
- struct lttng_dynamic_buffer reply_payload;
-
- lttng_dynamic_buffer_init(&reply_payload);
-
- if (conn->minor < 4) {
- /* From 2.1 to 2.3 */
- ret = 0;
- } else if (conn->minor >= 4 && conn->minor < 11) {
- /* From 2.4 to 2.10 */
- ret = cmd_create_session_2_4(payload, session_name,
- hostname, &live_timer, &snapshot);
- } else {
- bool has_current_chunk;
- uint64_t current_chunk_id_value;
- time_t creation_time_value;
- uint64_t id_sessiond_value;
-
- /* From 2.11 to ... */
- ret = cmd_create_session_2_11(payload, session_name, hostname,
- base_path, &live_timer, &snapshot, &id_sessiond_value,
- sessiond_uuid, &has_current_chunk,
- ¤t_chunk_id_value, &creation_time_value,
- &session_name_contains_creation_timestamp);
- if (lttng_uuid_is_nil(sessiond_uuid)) {
- /* The nil UUID is reserved for pre-2.11 clients. */
- ERR("Illegal nil UUID announced by peer in create session command");
- ret = -1;
- goto send_reply;
- }
- LTTNG_OPTIONAL_SET(&id_sessiond, id_sessiond_value);
- LTTNG_OPTIONAL_SET(&creation_time, creation_time_value);
- if (has_current_chunk) {
- LTTNG_OPTIONAL_SET(¤t_chunk_id,
- current_chunk_id_value);
- }
- }
-
- if (ret < 0) {
- goto send_reply;
- }
-
- session = session_create(session_name, hostname, base_path, live_timer,
- snapshot, sessiond_uuid,
- id_sessiond.is_set ? &id_sessiond.value : NULL,
- current_chunk_id.is_set ? ¤t_chunk_id.value : NULL,
- creation_time.is_set ? &creation_time.value : NULL,
- conn->major, conn->minor,
- session_name_contains_creation_timestamp);
- if (!session) {
- ret = -1;
- goto send_reply;
- }
- LTTNG_ASSERT(!conn->session);
- conn->session = session;
- DBG("Created session %" PRIu64, session->id);
-
- reply.generic.session_id = htobe64(session->id);
-
-send_reply:
- if (ret < 0) {
- reply.generic.ret_code = htobe32(LTTNG_ERR_FATAL);
- } else {
- reply.generic.ret_code = htobe32(LTTNG_OK);
- }
-
- if (conn->minor < 11) {
- /* From 2.1 to 2.10 */
- ret = lttng_dynamic_buffer_append(&reply_payload,
- &reply.generic, sizeof(reply.generic));
- if (ret) {
- ERR("Failed to append \"create session\" command reply header to payload buffer");
- ret = -1;
- goto end;
- }
- } else {
- const uint32_t output_path_length =
- session ? strlen(session->output_path) + 1 : 0;
-
- reply.output_path_length = htobe32(output_path_length);
- ret = lttng_dynamic_buffer_append(
- &reply_payload, &reply, sizeof(reply));
- if (ret) {
- ERR("Failed to append \"create session\" command reply header to payload buffer");
- goto end;
- }
-
- if (output_path_length) {
- ret = lttng_dynamic_buffer_append(&reply_payload,
- session->output_path,
- output_path_length);
- if (ret) {
- ERR("Failed to append \"create session\" command reply path to payload buffer");
- goto end;
- }
- }
- }
-
- send_ret = conn->sock->ops->sendmsg(conn->sock, reply_payload.data,
- reply_payload.size, 0);
- if (send_ret < (ssize_t) reply_payload.size) {
- ERR("Failed to send \"create session\" command reply of %zu bytes (ret = %zd)",
- reply_payload.size, send_ret);
- ret = -1;
- }
-end:
- if (ret < 0 && session) {
- session_put(session);
- }
- lttng_dynamic_buffer_reset(&reply_payload);
- return ret;
-}
-
-/*
- * When we have received all the streams and the metadata for a channel,
- * we make them visible to the viewer threads.
- */
-static void publish_connection_local_streams(struct relay_connection *conn)
-{
- struct relay_stream *stream;
- struct relay_session *session = conn->session;
-
- /*
- * We publish all streams belonging to a session atomically wrt
- * session lock.
- */
- pthread_mutex_lock(&session->lock);
- rcu_read_lock();
- cds_list_for_each_entry_rcu(stream, &session->recv_list,
- recv_node) {
- stream_publish(stream);
- }
- rcu_read_unlock();
-
- /*
- * Inform the viewer that there are new streams in the session.
- */
- if (session->viewer_attached) {
- uatomic_set(&session->new_streams, 1);
- }
- pthread_mutex_unlock(&session->lock);
-}
-
-static int conform_channel_path(char *channel_path)
-{
- int ret = 0;
-
- if (strstr("../", channel_path)) {
- ERR("Refusing channel path as it walks up the path hierarchy: \"%s\"",
- channel_path);
- ret = -1;
- goto end;
- }
-
- if (*channel_path == '/') {
- const size_t len = strlen(channel_path);
-
- /*
- * Channel paths from peers prior to 2.11 are expressed as an
- * absolute path that is, in reality, relative to the relay
- * daemon's output directory. Remove the leading slash so it
- * is correctly interpreted as a relative path later on.
- *
- * len (and not len - 1) is used to copy the trailing NULL.
- */
- bcopy(channel_path + 1, channel_path, len);
- }
-end:
- return ret;
-}
-
-/*
- * relay_add_stream: allocate a new stream for a session
- */
-static int relay_add_stream(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct relay_stream *stream = NULL;
- struct lttcomm_relayd_status_stream reply;
- struct ctf_trace *trace = NULL;
- uint64_t stream_handle = -1ULL;
- char *path_name = NULL, *channel_name = NULL;
- uint64_t tracefile_size = 0, tracefile_count = 0;
- LTTNG_OPTIONAL(uint64_t) stream_chunk_id = {};
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to add a stream before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (session->minor == 1) {
- /* For 2.1 */
- ret = cmd_recv_stream_2_1(payload, &path_name,
- &channel_name);
- } else if (session->minor > 1 && session->minor < 11) {
- /* From 2.2 to 2.10 */
- ret = cmd_recv_stream_2_2(payload, &path_name,
- &channel_name, &tracefile_size, &tracefile_count);
- } else {
- /* From 2.11 to ... */
- ret = cmd_recv_stream_2_11(payload, &path_name,
- &channel_name, &tracefile_size, &tracefile_count,
- &stream_chunk_id.value);
- stream_chunk_id.is_set = true;
- }
-
- if (ret < 0) {
- goto send_reply;
- }
-
- if (conform_channel_path(path_name)) {
- goto send_reply;
- }
-
- /*
- * Backward compatibility for --group-output-by-session.
- * Prior to lttng 2.11, the complete path is passed by the stream.
- * Starting at 2.11, lttng-relayd uses chunk. When dealing with producer
- * >=2.11 the chunk is responsible for the output path. When dealing
- * with producer < 2.11 the chunk output_path is the root output path
- * and the stream carries the complete path (path_name).
- * To support --group-output-by-session with older producer (<2.11), we
- * need to craft the path based on the stream path.
- */
- if (opt_group_output_by == RELAYD_GROUP_OUTPUT_BY_SESSION) {
- if (conn->minor < 4) {
- /*
- * From 2.1 to 2.3, the session_name is not passed on
- * the RELAYD_CREATE_SESSION command. The session name
- * is necessary to detect the presence of a base_path
- * inside the stream path. Without it we cannot perform
- * a valid group-output-by-session transformation.
- */
- WARN("Unable to perform a --group-by-session transformation for session %" PRIu64
- " for stream with path \"%s\" as it is produced by a peer using a protocol older than v2.4",
- session->id, path_name);
- } else if (conn->minor >= 4 && conn->minor < 11) {
- char *group_by_session_path_name;
-
- LTTNG_ASSERT(session->session_name[0] != '\0');
-
- group_by_session_path_name =
- backward_compat_group_by_session(
- path_name,
- session->session_name,
- session->creation_time.value);
- if (!group_by_session_path_name) {
- ERR("Failed to apply group by session to stream of session %" PRIu64,
- session->id);
- goto send_reply;
- }
-
- DBG("Transformed session path from \"%s\" to \"%s\" to honor per-session name grouping",
- path_name, group_by_session_path_name);
-
- free(path_name);
- path_name = group_by_session_path_name;
- }
- }
-
- trace = ctf_trace_get_by_path_or_create(session, path_name);
- if (!trace) {
- goto send_reply;
- }
-
- /* This stream here has one reference on the trace. */
- pthread_mutex_lock(&last_relay_stream_id_lock);
- stream_handle = ++last_relay_stream_id;
- pthread_mutex_unlock(&last_relay_stream_id_lock);
-
- /* We pass ownership of path_name and channel_name. */
- stream = stream_create(trace, stream_handle, path_name,
- channel_name, tracefile_size, tracefile_count);
- path_name = NULL;
- channel_name = NULL;
-
- /*
- * Streams are the owners of their trace. Reference to trace is
- * kept within stream_create().
- */
- ctf_trace_put(trace);
-
-send_reply:
- memset(&reply, 0, sizeof(reply));
- reply.handle = htobe64(stream_handle);
- if (!stream) {
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- } else {
- reply.ret_code = htobe32(LTTNG_OK);
- }
-
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(struct lttcomm_relayd_status_stream), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"add stream\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-
-end_no_session:
- free(path_name);
- free(channel_name);
- return ret;
-}
-
-/*
- * relay_close_stream: close a specific stream
- */
-static int relay_close_stream(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_close_stream stream_info;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
-
- DBG("Close stream received");
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to close a stream before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(stream_info)) {
- ERR("Unexpected payload size in \"relay_close_stream\": expected >= %zu bytes, got %zu bytes",
- sizeof(stream_info), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&stream_info, payload->data, sizeof(stream_info));
- stream_info.stream_id = be64toh(stream_info.stream_id);
- stream_info.last_net_seq_num = be64toh(stream_info.last_net_seq_num);
-
- stream = stream_get_by_id(stream_info.stream_id);
- if (!stream) {
- ret = -1;
- goto end;
- }
-
- /*
- * Set last_net_seq_num before the close flag. Required by data
- * pending check.
- */
- pthread_mutex_lock(&stream->lock);
- stream->last_net_seq_num = stream_info.last_net_seq_num;
- pthread_mutex_unlock(&stream->lock);
-
- /*
- * This is one of the conditions which may trigger a stream close
- * with the others being:
- * 1) A close command is received for a stream
- * 2) The control connection owning the stream is closed
- * 3) We have received all of the stream's data _after_ a close
- * request.
- */
- try_stream_close(stream);
- if (stream->is_metadata) {
- struct relay_viewer_stream *vstream;
-
- vstream = viewer_stream_get_by_id(stream->stream_handle);
- if (vstream) {
- if (stream->no_new_metadata_notified) {
- /*
- * Since all the metadata has been sent to the
- * viewer and that we have a request to close
- * its stream, we can safely teardown the
- * corresponding metadata viewer stream.
- */
- viewer_stream_put(vstream);
- }
- /* Put local reference. */
- viewer_stream_put(vstream);
- }
- }
- stream_put(stream);
- ret = 0;
-
-end:
- memset(&reply, 0, sizeof(reply));
- if (ret < 0) {
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- } else {
- reply.ret_code = htobe32(LTTNG_OK);
- }
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(struct lttcomm_relayd_generic_reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"close stream\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * relay_reset_metadata: reset a metadata stream
- */
-static
-int relay_reset_metadata(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_reset_metadata stream_info;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
-
- DBG("Reset metadata received");
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to reset a metadata stream before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(stream_info)) {
- ERR("Unexpected payload size in \"relay_reset_metadata\": expected >= %zu bytes, got %zu bytes",
- sizeof(stream_info), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&stream_info, payload->data, sizeof(stream_info));
- stream_info.stream_id = be64toh(stream_info.stream_id);
- stream_info.version = be64toh(stream_info.version);
-
- DBG("Update metadata to version %" PRIu64, stream_info.version);
-
- /* Unsupported for live sessions for now. */
- if (session->live_timer != 0) {
- ret = -1;
- goto end;
- }
-
- stream = stream_get_by_id(stream_info.stream_id);
- if (!stream) {
- ret = -1;
- goto end;
- }
- pthread_mutex_lock(&stream->lock);
- if (!stream->is_metadata) {
- ret = -1;
- goto end_unlock;
- }
-
- ret = stream_reset_file(stream);
- if (ret < 0) {
- ERR("Failed to reset metadata stream %" PRIu64
- ": stream_path = %s, channel = %s",
- stream->stream_handle, stream->path_name,
- stream->channel_name);
- goto end_unlock;
- }
-end_unlock:
- pthread_mutex_unlock(&stream->lock);
- stream_put(stream);
-
-end:
- memset(&reply, 0, sizeof(reply));
- if (ret < 0) {
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- } else {
- reply.ret_code = htobe32(LTTNG_OK);
- }
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(struct lttcomm_relayd_generic_reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"reset metadata\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * relay_unknown_command: send -1 if received unknown command
- */
-static void relay_unknown_command(struct relay_connection *conn)
-{
- struct lttcomm_relayd_generic_reply reply;
- ssize_t send_ret;
-
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < sizeof(reply)) {
- ERR("Failed to send \"unknown command\" command reply (ret = %zd)", send_ret);
- }
-}
-
-/*
- * relay_start: send an acknowledgment to the client to tell if we are
- * ready to receive data. We are ready if a session is established.
- */
-static int relay_start(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- ssize_t send_ret;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_session *session = conn->session;
-
- if (!session) {
- DBG("Trying to start the streaming without a session established");
- ret = htobe32(LTTNG_ERR_UNK);
- }
-
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(LTTNG_OK);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"relay_start\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-
- return ret;
-}
-
-/*
- * relay_recv_metadata: receive the metadata for the session.
- */
-static int relay_recv_metadata(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_metadata_payload metadata_payload_header;
- struct relay_stream *metadata_stream;
- uint64_t metadata_payload_size;
- struct lttng_buffer_view packet_view;
-
- if (!session) {
- ERR("Metadata sent before version check");
- ret = -1;
- goto end;
- }
-
- if (recv_hdr->data_size < sizeof(struct lttcomm_relayd_metadata_payload)) {
- ERR("Incorrect data size");
- ret = -1;
- goto end;
- }
- metadata_payload_size = recv_hdr->data_size -
- sizeof(struct lttcomm_relayd_metadata_payload);
-
- memcpy(&metadata_payload_header, payload->data,
- sizeof(metadata_payload_header));
- metadata_payload_header.stream_id = be64toh(
- metadata_payload_header.stream_id);
- metadata_payload_header.padding_size = be32toh(
- metadata_payload_header.padding_size);
-
- metadata_stream = stream_get_by_id(metadata_payload_header.stream_id);
- if (!metadata_stream) {
- ret = -1;
- goto end;
- }
-
- packet_view = lttng_buffer_view_from_view(payload,
- sizeof(metadata_payload_header), metadata_payload_size);
- if (!lttng_buffer_view_is_valid(&packet_view)) {
- ERR("Invalid metadata packet length announced by header");
- ret = -1;
- goto end_put;
- }
-
- pthread_mutex_lock(&metadata_stream->lock);
- ret = stream_write(metadata_stream, &packet_view,
- metadata_payload_header.padding_size);
- pthread_mutex_unlock(&metadata_stream->lock);
- if (ret){
- ret = -1;
- goto end_put;
- }
-end_put:
- stream_put(metadata_stream);
-end:
- return ret;
-}
-
-/*
- * relay_send_version: send relayd version number
- */
-static int relay_send_version(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct lttcomm_relayd_version reply, msg;
- bool compatible = true;
-
- conn->version_check_done = true;
-
- /* Get version from the other side. */
- if (payload->size < sizeof(msg)) {
- ERR("Unexpected payload size in \"relay_send_version\": expected >= %zu bytes, got %zu bytes",
- sizeof(msg), payload->size);
- ret = -1;
- goto end;
- }
-
- memcpy(&msg, payload->data, sizeof(msg));
- msg.major = be32toh(msg.major);
- msg.minor = be32toh(msg.minor);
-
- memset(&reply, 0, sizeof(reply));
- reply.major = RELAYD_VERSION_COMM_MAJOR;
- reply.minor = RELAYD_VERSION_COMM_MINOR;
-
- /* Major versions must be the same */
- if (reply.major != msg.major) {
- DBG("Incompatible major versions (%u vs %u), deleting session",
- reply.major, msg.major);
- compatible = false;
- }
-
- conn->major = reply.major;
- /* We adapt to the lowest compatible version */
- if (reply.minor <= msg.minor) {
- conn->minor = reply.minor;
- } else {
- conn->minor = msg.minor;
- }
-
- reply.major = htobe32(reply.major);
- reply.minor = htobe32(reply.minor);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"send version\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- goto end;
- } else {
- ret = 0;
- }
-
- if (!compatible) {
- ret = -1;
- goto end;
- }
-
- DBG("Version check done using protocol %u.%u", conn->major,
- conn->minor);
-
-end:
- return ret;
-}
-
-/*
- * Check for data pending for a given stream id from the session daemon.
- */
-static int relay_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_data_pending msg;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
- ssize_t send_ret;
- int ret;
- uint64_t stream_seq;
-
- DBG("Data pending command received");
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to check for data before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(msg)) {
- ERR("Unexpected payload size in \"relay_data_pending\": expected >= %zu bytes, got %zu bytes",
- sizeof(msg), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&msg, payload->data, sizeof(msg));
- msg.stream_id = be64toh(msg.stream_id);
- msg.last_net_seq_num = be64toh(msg.last_net_seq_num);
-
- stream = stream_get_by_id(msg.stream_id);
- if (stream == NULL) {
- ret = -1;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
-
- if (session_streams_have_index(session)) {
- /*
- * Ensure that both the index and stream data have been
- * flushed up to the requested point.
- */
- stream_seq = min(stream->prev_data_seq, stream->prev_index_seq);
- } else {
- stream_seq = stream->prev_data_seq;
- }
- DBG("Data pending for stream id %" PRIu64 ": prev_data_seq %" PRIu64
- ", prev_index_seq %" PRIu64
- ", and last_seq %" PRIu64, msg.stream_id,
- stream->prev_data_seq, stream->prev_index_seq,
- msg.last_net_seq_num);
-
- /* Avoid wrapping issue */
- if (((int64_t) (stream_seq - msg.last_net_seq_num)) >= 0) {
- /* Data has in fact been written and is NOT pending */
- ret = 0;
- } else {
- /* Data still being streamed thus pending */
- ret = 1;
- }
-
- stream->data_pending_check_done = true;
- pthread_mutex_unlock(&stream->lock);
-
- stream_put(stream);
-end:
-
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(ret);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"data pending\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * Wait for the control socket to reach a quiescent state.
- *
- * Note that for now, when receiving this command from the session
- * daemon, this means that every subsequent commands or data received on
- * the control socket has been handled. So, this is why we simply return
- * OK here.
- */
-static int relay_quiescent_control(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct relay_stream *stream;
- struct lttcomm_relayd_quiescent_control msg;
- struct lttcomm_relayd_generic_reply reply;
-
- DBG("Checking quiescent state on control socket");
-
- if (!conn->session || !conn->version_check_done) {
- ERR("Trying to check for data before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(msg)) {
- ERR("Unexpected payload size in \"relay_quiescent_control\": expected >= %zu bytes, got %zu bytes",
- sizeof(msg), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&msg, payload->data, sizeof(msg));
- msg.stream_id = be64toh(msg.stream_id);
-
- stream = stream_get_by_id(msg.stream_id);
- if (!stream) {
- goto reply;
- }
- pthread_mutex_lock(&stream->lock);
- stream->data_pending_check_done = true;
- pthread_mutex_unlock(&stream->lock);
-
- DBG("Relay quiescent control pending flag set to %" PRIu64, msg.stream_id);
- stream_put(stream);
-reply:
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(LTTNG_OK);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"quiescent control\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- } else {
- ret = 0;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * Initialize a data pending command. This means that a consumer is about
- * to ask for data pending for each stream it holds. Simply iterate over
- * all streams of a session and set the data_pending_check_done flag.
- *
- * This command returns to the client a LTTNG_OK code.
- */
-static int relay_begin_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct lttng_ht_iter iter;
- struct lttcomm_relayd_begin_data_pending msg;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
-
- LTTNG_ASSERT(recv_hdr);
- LTTNG_ASSERT(conn);
-
- DBG("Init streams for data pending");
-
- if (!conn->session || !conn->version_check_done) {
- ERR("Trying to check for data before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(msg)) {
- ERR("Unexpected payload size in \"relay_begin_data_pending\": expected >= %zu bytes, got %zu bytes",
- sizeof(msg), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&msg, payload->data, sizeof(msg));
- msg.session_id = be64toh(msg.session_id);
-
- /*
- * Iterate over all streams to set the begin data pending flag.
- * For now, the streams are indexed by stream handle so we have
- * to iterate over all streams to find the one associated with
- * the right session_id.
- */
- rcu_read_lock();
- cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
- node.node) {
- if (!stream_get(stream)) {
- continue;
- }
- if (stream->trace->session->id == msg.session_id) {
- pthread_mutex_lock(&stream->lock);
- stream->data_pending_check_done = false;
- pthread_mutex_unlock(&stream->lock);
- DBG("Set begin data pending flag to stream %" PRIu64,
- stream->stream_handle);
- }
- stream_put(stream);
- }
- rcu_read_unlock();
-
- memset(&reply, 0, sizeof(reply));
- /* All good, send back reply. */
- reply.ret_code = htobe32(LTTNG_OK);
-
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"begin data pending\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- } else {
- ret = 0;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * End data pending command. This will check, for a given session id, if
- * each stream associated with it has its data_pending_check_done flag
- * set. If not, this means that the client lost track of the stream but
- * the data is still being streamed on our side. In this case, we inform
- * the client that data is in flight.
- *
- * Return to the client if there is data in flight or not with a ret_code.
- */
-static int relay_end_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct lttng_ht_iter iter;
- struct lttcomm_relayd_end_data_pending msg;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
- uint32_t is_data_inflight = 0;
-
- DBG("End data pending command");
-
- if (!conn->session || !conn->version_check_done) {
- ERR("Trying to check for data before version check");
- ret = -1;
- goto end_no_session;
- }
-
- if (payload->size < sizeof(msg)) {
- ERR("Unexpected payload size in \"relay_end_data_pending\": expected >= %zu bytes, got %zu bytes",
- sizeof(msg), payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&msg, payload->data, sizeof(msg));
- msg.session_id = be64toh(msg.session_id);
-
- /*
- * Iterate over all streams to see if the begin data pending
- * flag is set.
- */
- rcu_read_lock();
- cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
- node.node) {
- if (!stream_get(stream)) {
- continue;
- }
- if (stream->trace->session->id != msg.session_id) {
- stream_put(stream);
- continue;
- }
- pthread_mutex_lock(&stream->lock);
- if (!stream->data_pending_check_done) {
- uint64_t stream_seq;
-
- if (session_streams_have_index(conn->session)) {
- /*
- * Ensure that both the index and stream data have been
- * flushed up to the requested point.
- */
- stream_seq = min(stream->prev_data_seq, stream->prev_index_seq);
- } else {
- stream_seq = stream->prev_data_seq;
- }
- if (!stream->closed || !(((int64_t) (stream_seq - stream->last_net_seq_num)) >= 0)) {
- is_data_inflight = 1;
- DBG("Data is still in flight for stream %" PRIu64,
- stream->stream_handle);
- pthread_mutex_unlock(&stream->lock);
- stream_put(stream);
- break;
- }
- }
- pthread_mutex_unlock(&stream->lock);
- stream_put(stream);
- }
- rcu_read_unlock();
-
- memset(&reply, 0, sizeof(reply));
- /* All good, send back reply. */
- reply.ret_code = htobe32(is_data_inflight);
-
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"end data pending\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- } else {
- ret = 0;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * Receive an index for a specific stream.
- *
- * Return 0 on success else a negative value.
- */
-static int relay_recv_index(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_index index_info;
- struct lttcomm_relayd_generic_reply reply;
- struct relay_stream *stream;
- size_t msg_len;
-
- LTTNG_ASSERT(conn);
-
- DBG("Relay receiving index");
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to close a stream before version check");
- ret = -1;
- goto end_no_session;
- }
-
- msg_len = lttcomm_relayd_index_len(
- lttng_to_index_major(conn->major, conn->minor),
- lttng_to_index_minor(conn->major, conn->minor));
- if (payload->size < msg_len) {
- ERR("Unexpected payload size in \"relay_recv_index\": expected >= %zu bytes, got %zu bytes",
- msg_len, payload->size);
- ret = -1;
- goto end_no_session;
- }
- memcpy(&index_info, payload->data, msg_len);
- index_info.relay_stream_id = be64toh(index_info.relay_stream_id);
- index_info.net_seq_num = be64toh(index_info.net_seq_num);
- index_info.packet_size = be64toh(index_info.packet_size);
- index_info.content_size = be64toh(index_info.content_size);
- index_info.timestamp_begin = be64toh(index_info.timestamp_begin);
- index_info.timestamp_end = be64toh(index_info.timestamp_end);
- index_info.events_discarded = be64toh(index_info.events_discarded);
- index_info.stream_id = be64toh(index_info.stream_id);
-
- if (conn->minor >= 8) {
- index_info.stream_instance_id =
- be64toh(index_info.stream_instance_id);
- index_info.packet_seq_num = be64toh(index_info.packet_seq_num);
- } else {
- index_info.stream_instance_id = -1ULL;
- index_info.packet_seq_num = -1ULL;
- }
-
- stream = stream_get_by_id(index_info.relay_stream_id);
- if (!stream) {
- ERR("stream_get_by_id not found");
- ret = -1;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
- ret = stream_add_index(stream, &index_info);
- pthread_mutex_unlock(&stream->lock);
- if (ret) {
- goto end_stream_put;
- }
-
-end_stream_put:
- stream_put(stream);
-end:
- memset(&reply, 0, sizeof(reply));
- if (ret < 0) {
- reply.ret_code = htobe32(LTTNG_ERR_UNK);
- } else {
- reply.ret_code = htobe32(LTTNG_OK);
- }
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"recv index\" command reply (ret = %zd)", send_ret);
- ret = -1;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * Receive the streams_sent message.
- *
- * Return 0 on success else a negative value.
- */
-static int relay_streams_sent(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret;
- ssize_t send_ret;
- struct lttcomm_relayd_generic_reply reply;
-
- LTTNG_ASSERT(conn);
-
- DBG("Relay receiving streams_sent");
-
- if (!conn->session || !conn->version_check_done) {
- ERR("Trying to close a stream before version check");
- ret = -1;
- goto end_no_session;
- }
-
- /*
- * Publish every pending stream in the connection recv list which are
- * now ready to be used by the viewer.
- */
- publish_connection_local_streams(conn);
-
- memset(&reply, 0, sizeof(reply));
- reply.ret_code = htobe32(LTTNG_OK);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"streams sent\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- } else {
- /* Success. */
- ret = 0;
- }
-
-end_no_session:
- return ret;
-}
-
-/*
- * relay_rotate_session_stream: rotate a stream to a new tracefile for the
- * session rotation feature (not the tracefile rotation feature).
- */
-static int relay_rotate_session_streams(
- const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- uint32_t i;
- ssize_t send_ret;
- enum lttng_error_code reply_code = LTTNG_ERR_UNK;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_rotate_streams rotate_streams;
- struct lttcomm_relayd_generic_reply reply = {};
- struct relay_stream *stream = NULL;
- const size_t header_len = sizeof(struct lttcomm_relayd_rotate_streams);
- struct lttng_trace_chunk *next_trace_chunk = NULL;
- struct lttng_buffer_view stream_positions;
- char chunk_id_buf[MAX_INT_DEC_LEN(uint64_t)];
- const char *chunk_id_str = "none";
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to rotate a stream before version check");
- ret = -1;
- goto end_no_reply;
- }
-
- if (session->major == 2 && session->minor < 11) {
- ERR("Unsupported feature before 2.11");
- ret = -1;
- goto end_no_reply;
- }
-
- if (payload->size < header_len) {
- ERR("Unexpected payload size in \"relay_rotate_session_stream\": expected >= %zu bytes, got %zu bytes",
- header_len, payload->size);
- ret = -1;
- goto end_no_reply;
- }
-
- memcpy(&rotate_streams, payload->data, header_len);
-
- /* Convert header to host endianness. */
- rotate_streams = (typeof(rotate_streams)) {
- .stream_count = be32toh(rotate_streams.stream_count),
- .new_chunk_id = (typeof(rotate_streams.new_chunk_id)) {
- .is_set = !!rotate_streams.new_chunk_id.is_set,
- .value = be64toh(rotate_streams.new_chunk_id.value),
- }
- };
-
- if (rotate_streams.new_chunk_id.is_set) {
- /*
- * Retrieve the trace chunk the stream must transition to. As
- * per the protocol, this chunk should have been created
- * before this command is received.
- */
- next_trace_chunk = sessiond_trace_chunk_registry_get_chunk(
- sessiond_trace_chunk_registry,
- session->sessiond_uuid, session->id,
- rotate_streams.new_chunk_id.value);
- if (!next_trace_chunk) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(session->sessiond_uuid, uuid_str);
- ERR("Unknown next trace chunk in ROTATE_STREAMS command: sessiond_uuid = {%s}, session_id = %" PRIu64
- ", trace_chunk_id = %" PRIu64,
- uuid_str, session->id,
- rotate_streams.new_chunk_id.value);
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- ret = -1;
- goto end;
- }
-
- ret = snprintf(chunk_id_buf, sizeof(chunk_id_buf), "%" PRIu64,
- rotate_streams.new_chunk_id.value);
- if (ret < 0 || ret >= sizeof(chunk_id_buf)) {
- chunk_id_str = "formatting error";
- } else {
- chunk_id_str = chunk_id_buf;
- }
- }
-
- DBG("Rotate %" PRIu32 " streams of session \"%s\" to chunk \"%s\"",
- rotate_streams.stream_count, session->session_name,
- chunk_id_str);
-
- stream_positions = lttng_buffer_view_from_view(payload,
- sizeof(rotate_streams), -1);
- if (!stream_positions.data ||
- stream_positions.size <
- (rotate_streams.stream_count *
- sizeof(struct lttcomm_relayd_stream_rotation_position))) {
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- ret = -1;
- goto end;
- }
-
- for (i = 0; i < rotate_streams.stream_count; i++) {
- struct lttcomm_relayd_stream_rotation_position *position_comm =
- &((typeof(position_comm)) stream_positions.data)[i];
- const struct lttcomm_relayd_stream_rotation_position pos = {
- .stream_id = be64toh(position_comm->stream_id),
- .rotate_at_seq_num = be64toh(
- position_comm->rotate_at_seq_num),
- };
-
- stream = stream_get_by_id(pos.stream_id);
- if (!stream) {
- reply_code = LTTNG_ERR_INVALID;
- ret = -1;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
- ret = stream_set_pending_rotation(stream, next_trace_chunk,
- pos.rotate_at_seq_num);
- pthread_mutex_unlock(&stream->lock);
- if (ret) {
- reply_code = LTTNG_ERR_FILE_CREATION_ERROR;
- goto end;
- }
-
- stream_put(stream);
- stream = NULL;
- }
-
- reply_code = LTTNG_OK;
- ret = 0;
-end:
- if (stream) {
- stream_put(stream);
- }
-
- reply.ret_code = htobe32((uint32_t) reply_code);
- send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
- sizeof(struct lttcomm_relayd_generic_reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"rotate session stream\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-end_no_reply:
- lttng_trace_chunk_put(next_trace_chunk);
- return ret;
-}
-
-
-
-/*
- * relay_create_trace_chunk: create a new trace chunk
- */
-static int relay_create_trace_chunk(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_create_trace_chunk *msg;
- struct lttcomm_relayd_generic_reply reply = {};
- struct lttng_buffer_view header_view;
- struct lttng_trace_chunk *chunk = NULL, *published_chunk = NULL;
- enum lttng_error_code reply_code = LTTNG_OK;
- enum lttng_trace_chunk_status chunk_status;
- const char *new_path;
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to create a trace chunk before version check");
- ret = -1;
- goto end_no_reply;
- }
-
- if (session->major == 2 && session->minor < 11) {
- ERR("Chunk creation command is unsupported before 2.11");
- ret = -1;
- goto end_no_reply;
- }
-
- header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
- if (!lttng_buffer_view_is_valid(&header_view)) {
- ERR("Failed to receive payload of chunk creation command");
- ret = -1;
- goto end_no_reply;
- }
-
- /* Convert to host endianness. */
- msg = (typeof(msg)) header_view.data;
- msg->chunk_id = be64toh(msg->chunk_id);
- msg->creation_timestamp = be64toh(msg->creation_timestamp);
- msg->override_name_length = be32toh(msg->override_name_length);
-
- if (session->current_trace_chunk &&
- !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) {
- chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk,
- DEFAULT_CHUNK_TMP_OLD_DIRECTORY);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ERR("Failed to rename old chunk");
- ret = -1;
- reply_code = LTTNG_ERR_UNK;
- goto end;
- }
- }
- session->ongoing_rotation = true;
- if (!session->current_trace_chunk) {
- if (!session->has_rotated) {
- new_path = "";
- } else {
- new_path = NULL;
- }
- } else {
- new_path = DEFAULT_CHUNK_TMP_NEW_DIRECTORY;
- }
- chunk = lttng_trace_chunk_create(
- msg->chunk_id, msg->creation_timestamp, new_path);
- if (!chunk) {
- ERR("Failed to create trace chunk in trace chunk creation command");
- ret = -1;
- reply_code = LTTNG_ERR_NOMEM;
- goto end;
- }
- lttng_trace_chunk_set_fd_tracker(chunk, the_fd_tracker);
-
- if (msg->override_name_length) {
- const char *name;
- const struct lttng_buffer_view chunk_name_view =
- lttng_buffer_view_from_view(payload,
- sizeof(*msg),
- msg->override_name_length);
-
- if (!lttng_buffer_view_is_valid(&chunk_name_view)) {
- ERR("Invalid payload of chunk creation command (protocol error): buffer too short for expected name length");
- ret = -1;
- reply_code = LTTNG_ERR_INVALID;
- goto end;
- }
-
- name = chunk_name_view.data;
- if (name[msg->override_name_length - 1]) {
- ERR("Invalid payload of chunk creation command (protocol error): name is not null-terminated");
- ret = -1;
- reply_code = LTTNG_ERR_INVALID;
- goto end;
- }
-
- chunk_status = lttng_trace_chunk_override_name(
- chunk, chunk_name_view.data);
- switch (chunk_status) {
- case LTTNG_TRACE_CHUNK_STATUS_OK:
- break;
- case LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT:
- ERR("Failed to set the name of new trace chunk in trace chunk creation command (invalid name)");
- reply_code = LTTNG_ERR_INVALID;
- ret = -1;
- goto end;
- default:
- ERR("Failed to set the name of new trace chunk in trace chunk creation command (unknown error)");
- reply_code = LTTNG_ERR_UNK;
- ret = -1;
- goto end;
- }
- }
-
- chunk_status = lttng_trace_chunk_set_credentials_current_user(chunk);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- reply_code = LTTNG_ERR_UNK;
- ret = -1;
- goto end;
- }
-
- LTTNG_ASSERT(conn->session->output_directory);
- chunk_status = lttng_trace_chunk_set_as_owner(chunk,
- conn->session->output_directory);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- reply_code = LTTNG_ERR_UNK;
- ret = -1;
- goto end;
- }
-
- published_chunk = sessiond_trace_chunk_registry_publish_chunk(
- sessiond_trace_chunk_registry,
- conn->session->sessiond_uuid,
- conn->session->id,
- chunk);
- if (!published_chunk) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(conn->session->sessiond_uuid, uuid_str);
- ERR("Failed to publish chunk: sessiond_uuid = %s, session_id = %" PRIu64 ", chunk_id = %" PRIu64,
- uuid_str,
- conn->session->id,
- msg->chunk_id);
- ret = -1;
- reply_code = LTTNG_ERR_NOMEM;
- goto end;
- }
-
- pthread_mutex_lock(&conn->session->lock);
- if (conn->session->pending_closure_trace_chunk) {
- /*
- * Invalid; this means a second create_trace_chunk command was
- * received before a close_trace_chunk.
- */
- ERR("Invalid trace chunk close command received; a trace chunk is already waiting for a trace chunk close command");
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- ret = -1;
- goto end_unlock_session;
- }
- conn->session->pending_closure_trace_chunk =
- conn->session->current_trace_chunk;
- conn->session->current_trace_chunk = published_chunk;
- published_chunk = NULL;
- if (!conn->session->pending_closure_trace_chunk) {
- session->ongoing_rotation = false;
- }
-end_unlock_session:
- pthread_mutex_unlock(&conn->session->lock);
-end:
- reply.ret_code = htobe32((uint32_t) reply_code);
- send_ret = conn->sock->ops->sendmsg(conn->sock,
- &reply,
- sizeof(struct lttcomm_relayd_generic_reply),
- 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"create trace chunk\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-end_no_reply:
- lttng_trace_chunk_put(chunk);
- lttng_trace_chunk_put(published_chunk);
- return ret;
-}
-
-/*
- * relay_close_trace_chunk: close a trace chunk
- */
-static int relay_close_trace_chunk(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0, buf_ret;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_close_trace_chunk *msg;
- struct lttcomm_relayd_close_trace_chunk_reply reply = {};
- struct lttng_buffer_view header_view;
- struct lttng_trace_chunk *chunk = NULL;
- enum lttng_error_code reply_code = LTTNG_OK;
- enum lttng_trace_chunk_status chunk_status;
- uint64_t chunk_id;
- LTTNG_OPTIONAL(enum lttng_trace_chunk_command_type) close_command = {};
- time_t close_timestamp;
- char closed_trace_chunk_path[LTTNG_PATH_MAX];
- size_t path_length = 0;
- const char *chunk_name = NULL;
- struct lttng_dynamic_buffer reply_payload;
- const char *new_path;
-
- lttng_dynamic_buffer_init(&reply_payload);
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to close a trace chunk before version check");
- ret = -1;
- goto end_no_reply;
- }
-
- if (session->major == 2 && session->minor < 11) {
- ERR("Chunk close command is unsupported before 2.11");
- ret = -1;
- goto end_no_reply;
- }
-
- header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
- if (!lttng_buffer_view_is_valid(&header_view)) {
- ERR("Failed to receive payload of chunk close command");
- ret = -1;
- goto end_no_reply;
- }
-
- /* Convert to host endianness. */
- msg = (typeof(msg)) header_view.data;
- chunk_id = be64toh(msg->chunk_id);
- close_timestamp = (time_t) be64toh(msg->close_timestamp);
- close_command = (typeof(close_command)){
- .value = be32toh(msg->close_command.value),
- .is_set = msg->close_command.is_set,
- };
-
- chunk = sessiond_trace_chunk_registry_get_chunk(
- sessiond_trace_chunk_registry,
- conn->session->sessiond_uuid,
- conn->session->id,
- chunk_id);
- if (!chunk) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(conn->session->sessiond_uuid, uuid_str);
- ERR("Failed to find chunk to close: sessiond_uuid = %s, session_id = %" PRIu64 ", chunk_id = %" PRIu64,
- uuid_str,
- conn->session->id,
- msg->chunk_id);
- ret = -1;
- reply_code = LTTNG_ERR_NOMEM;
- goto end;
- }
-
- pthread_mutex_lock(&session->lock);
- if (close_command.is_set &&
- close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE) {
- /*
- * Clear command. It is a protocol error to ask for a
- * clear on a relay which does not allow it. Querying
- * the configuration allows figuring out whether
- * clearing is allowed before doing the clear.
- */
- if (!opt_allow_clear) {
- ret = -1;
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- goto end_unlock_session;
- }
- }
- if (session->pending_closure_trace_chunk &&
- session->pending_closure_trace_chunk != chunk) {
- ERR("Trace chunk close command for session \"%s\" does not target the trace chunk pending closure",
- session->session_name);
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- ret = -1;
- goto end_unlock_session;
- }
-
- if (session->current_trace_chunk && session->current_trace_chunk != chunk &&
- !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) {
- if (close_command.is_set &&
- close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE &&
- !session->has_rotated) {
- /* New chunk stays in session output directory. */
- new_path = "";
- } else {
- /* Use chunk name for new chunk. */
- new_path = NULL;
- }
- /* Rename new chunk path. */
- chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk,
- new_path);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end_unlock_session;
- }
- session->ongoing_rotation = false;
- }
- if ((!close_command.is_set ||
- close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION) &&
- !lttng_trace_chunk_get_name_overridden(chunk)) {
- const char *old_path;
-
- if (!session->has_rotated) {
- old_path = "";
- } else {
- old_path = NULL;
- }
- /* We need to move back the .tmp_old_chunk to its rightful place. */
- chunk_status = lttng_trace_chunk_rename_path(chunk, old_path);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end_unlock_session;
- }
- }
- chunk_status = lttng_trace_chunk_set_close_timestamp(
- chunk, close_timestamp);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ERR("Failed to set trace chunk close timestamp");
- ret = -1;
- reply_code = LTTNG_ERR_UNK;
- goto end_unlock_session;
- }
-
- if (close_command.is_set) {
- chunk_status = lttng_trace_chunk_set_close_command(
- chunk, close_command.value);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- reply_code = LTTNG_ERR_INVALID;
- goto end_unlock_session;
- }
- }
- chunk_status = lttng_trace_chunk_get_name(chunk, &chunk_name, NULL);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ERR("Failed to get chunk name");
- ret = -1;
- reply_code = LTTNG_ERR_UNK;
- goto end_unlock_session;
- }
- if (!session->has_rotated && !session->snapshot) {
- ret = lttng_strncpy(closed_trace_chunk_path,
- session->output_path,
- sizeof(closed_trace_chunk_path));
- if (ret) {
- ERR("Failed to send trace chunk path: path length of %zu bytes exceeds the maximal allowed length of %zu bytes",
- strlen(session->output_path),
- sizeof(closed_trace_chunk_path));
- reply_code = LTTNG_ERR_NOMEM;
- ret = -1;
- goto end_unlock_session;
- }
- } else {
- if (session->snapshot) {
- ret = snprintf(closed_trace_chunk_path,
- sizeof(closed_trace_chunk_path),
- "%s/%s", session->output_path,
- chunk_name);
- } else {
- ret = snprintf(closed_trace_chunk_path,
- sizeof(closed_trace_chunk_path),
- "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY
- "/%s",
- session->output_path, chunk_name);
- }
- if (ret < 0 || ret == sizeof(closed_trace_chunk_path)) {
- ERR("Failed to format closed trace chunk resulting path");
- reply_code = ret < 0 ? LTTNG_ERR_UNK : LTTNG_ERR_NOMEM;
- ret = -1;
- goto end_unlock_session;
- }
- }
- if (close_command.is_set &&
- close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED) {
- session->has_rotated = true;
- }
- DBG("Reply chunk path on close: %s", closed_trace_chunk_path);
- path_length = strlen(closed_trace_chunk_path) + 1;
- if (path_length > UINT32_MAX) {
- ERR("Closed trace chunk path exceeds the maximal length allowed by the protocol");
- ret = -1;
- reply_code = LTTNG_ERR_INVALID_PROTOCOL;
- goto end_unlock_session;
- }
-
- if (session->current_trace_chunk == chunk) {
- /*
- * After a trace chunk close command, no new streams
- * referencing the chunk may be created. Hence, on the
- * event that no new trace chunk have been created for
- * the session, the reference to the current trace chunk
- * is released in order to allow it to be reclaimed when
- * the last stream releases its reference to it.
- */
- lttng_trace_chunk_put(session->current_trace_chunk);
- session->current_trace_chunk = NULL;
- }
- lttng_trace_chunk_put(session->pending_closure_trace_chunk);
- session->pending_closure_trace_chunk = NULL;
-end_unlock_session:
- pthread_mutex_unlock(&session->lock);
-
-end:
- reply.generic.ret_code = htobe32((uint32_t) reply_code);
- reply.path_length = htobe32((uint32_t) path_length);
- buf_ret = lttng_dynamic_buffer_append(
- &reply_payload, &reply, sizeof(reply));
- if (buf_ret) {
- ERR("Failed to append \"close trace chunk\" command reply header to payload buffer");
- goto end_no_reply;
- }
-
- if (reply_code == LTTNG_OK) {
- buf_ret = lttng_dynamic_buffer_append(&reply_payload,
- closed_trace_chunk_path, path_length);
- if (buf_ret) {
- ERR("Failed to append \"close trace chunk\" command reply path to payload buffer");
- goto end_no_reply;
- }
- }
-
- send_ret = conn->sock->ops->sendmsg(conn->sock,
- reply_payload.data,
- reply_payload.size,
- 0);
- if (send_ret < reply_payload.size) {
- ERR("Failed to send \"close trace chunk\" command reply of %zu bytes (ret = %zd)",
- reply_payload.size, send_ret);
- ret = -1;
- goto end_no_reply;
- }
-end_no_reply:
- lttng_trace_chunk_put(chunk);
- lttng_dynamic_buffer_reset(&reply_payload);
- return ret;
-}
-
-/*
- * relay_trace_chunk_exists: check if a trace chunk exists
- */
-static int relay_trace_chunk_exists(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- ssize_t send_ret;
- struct relay_session *session = conn->session;
- struct lttcomm_relayd_trace_chunk_exists *msg;
- struct lttcomm_relayd_trace_chunk_exists_reply reply = {};
- struct lttng_buffer_view header_view;
- uint64_t chunk_id;
- bool chunk_exists;
-
- if (!session || !conn->version_check_done) {
- ERR("Trying to check for the existance of a trace chunk before version check");
- ret = -1;
- goto end_no_reply;
- }
-
- if (session->major == 2 && session->minor < 11) {
- ERR("Chunk exists command is unsupported before 2.11");
- ret = -1;
- goto end_no_reply;
- }
-
- header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
- if (!lttng_buffer_view_is_valid(&header_view)) {
- ERR("Failed to receive payload of chunk exists command");
- ret = -1;
- goto end_no_reply;
- }
-
- /* Convert to host endianness. */
- msg = (typeof(msg)) header_view.data;
- chunk_id = be64toh(msg->chunk_id);
-
- ret = sessiond_trace_chunk_registry_chunk_exists(
- sessiond_trace_chunk_registry,
- conn->session->sessiond_uuid,
- conn->session->id,
- chunk_id, &chunk_exists);
- /*
- * If ret is not 0, send the reply and report the error to the caller.
- * It is a protocol (or internal) error and the session/connection
- * should be torn down.
- */
- reply = (typeof(reply)){
- .generic.ret_code = htobe32((uint32_t)
- (ret == 0 ? LTTNG_OK : LTTNG_ERR_INVALID_PROTOCOL)),
- .trace_chunk_exists = ret == 0 ? chunk_exists : 0,
- };
- send_ret = conn->sock->ops->sendmsg(
- conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"create trace chunk\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-end_no_reply:
- return ret;
-}
-
-/*
- * relay_get_configuration: query whether feature is available
- */
-static int relay_get_configuration(const struct lttcomm_relayd_hdr *recv_hdr,
- struct relay_connection *conn,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
- ssize_t send_ret;
- struct lttcomm_relayd_get_configuration *msg;
- struct lttcomm_relayd_get_configuration_reply reply = {};
- struct lttng_buffer_view header_view;
- uint64_t query_flags = 0;
- uint64_t result_flags = 0;
-
- header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
- if (!lttng_buffer_view_is_valid(&header_view)) {
- ERR("Failed to receive payload of chunk close command");
- ret = -1;
- goto end_no_reply;
- }
-
- /* Convert to host endianness. */
- msg = (typeof(msg)) header_view.data;
- query_flags = be64toh(msg->query_flags);
-
- if (query_flags) {
- ret = LTTNG_ERR_INVALID_PROTOCOL;
- goto reply;
- }
- if (opt_allow_clear) {
- result_flags |= LTTCOMM_RELAYD_CONFIGURATION_FLAG_CLEAR_ALLOWED;
- }
- ret = 0;
-reply:
- reply = (typeof(reply)){
- .generic.ret_code = htobe32((uint32_t)
- (ret == 0 ? LTTNG_OK : LTTNG_ERR_INVALID_PROTOCOL)),
- .relayd_configuration_flags = htobe64(result_flags),
- };
- send_ret = conn->sock->ops->sendmsg(
- conn->sock, &reply, sizeof(reply), 0);
- if (send_ret < (ssize_t) sizeof(reply)) {
- ERR("Failed to send \"get configuration\" command reply (ret = %zd)",
- send_ret);
- ret = -1;
- }
-end_no_reply:
- return ret;
-}
-
-#define DBG_CMD(cmd_name, conn) \
- DBG3("Processing \"%s\" command for socket %i", cmd_name, conn->sock->fd);
-
-static int relay_process_control_command(struct relay_connection *conn,
- const struct lttcomm_relayd_hdr *header,
- const struct lttng_buffer_view *payload)
-{
- int ret = 0;
-
- switch (header->cmd) {
- case RELAYD_CREATE_SESSION:
- DBG_CMD("RELAYD_CREATE_SESSION", conn);
- ret = relay_create_session(header, conn, payload);
- break;
- case RELAYD_ADD_STREAM:
- DBG_CMD("RELAYD_ADD_STREAM", conn);
- ret = relay_add_stream(header, conn, payload);
- break;
- case RELAYD_START_DATA:
- DBG_CMD("RELAYD_START_DATA", conn);
- ret = relay_start(header, conn, payload);
- break;
- case RELAYD_SEND_METADATA:
- DBG_CMD("RELAYD_SEND_METADATA", conn);
- ret = relay_recv_metadata(header, conn, payload);
- break;
- case RELAYD_VERSION:
- DBG_CMD("RELAYD_VERSION", conn);
- ret = relay_send_version(header, conn, payload);
- break;
- case RELAYD_CLOSE_STREAM:
- DBG_CMD("RELAYD_CLOSE_STREAM", conn);
- ret = relay_close_stream(header, conn, payload);
- break;
- case RELAYD_DATA_PENDING:
- DBG_CMD("RELAYD_DATA_PENDING", conn);
- ret = relay_data_pending(header, conn, payload);
- break;
- case RELAYD_QUIESCENT_CONTROL:
- DBG_CMD("RELAYD_QUIESCENT_CONTROL", conn);
- ret = relay_quiescent_control(header, conn, payload);
- break;
- case RELAYD_BEGIN_DATA_PENDING:
- DBG_CMD("RELAYD_BEGIN_DATA_PENDING", conn);
- ret = relay_begin_data_pending(header, conn, payload);
- break;
- case RELAYD_END_DATA_PENDING:
- DBG_CMD("RELAYD_END_DATA_PENDING", conn);
- ret = relay_end_data_pending(header, conn, payload);
- break;
- case RELAYD_SEND_INDEX:
- DBG_CMD("RELAYD_SEND_INDEX", conn);
- ret = relay_recv_index(header, conn, payload);
- break;
- case RELAYD_STREAMS_SENT:
- DBG_CMD("RELAYD_STREAMS_SENT", conn);
- ret = relay_streams_sent(header, conn, payload);
- break;
- case RELAYD_RESET_METADATA:
- DBG_CMD("RELAYD_RESET_METADATA", conn);
- ret = relay_reset_metadata(header, conn, payload);
- break;
- case RELAYD_ROTATE_STREAMS:
- DBG_CMD("RELAYD_ROTATE_STREAMS", conn);
- ret = relay_rotate_session_streams(header, conn, payload);
- break;
- case RELAYD_CREATE_TRACE_CHUNK:
- DBG_CMD("RELAYD_CREATE_TRACE_CHUNK", conn);
- ret = relay_create_trace_chunk(header, conn, payload);
- break;
- case RELAYD_CLOSE_TRACE_CHUNK:
- DBG_CMD("RELAYD_CLOSE_TRACE_CHUNK", conn);
- ret = relay_close_trace_chunk(header, conn, payload);
- break;
- case RELAYD_TRACE_CHUNK_EXISTS:
- DBG_CMD("RELAYD_TRACE_CHUNK_EXISTS", conn);
- ret = relay_trace_chunk_exists(header, conn, payload);
- break;
- case RELAYD_GET_CONFIGURATION:
- DBG_CMD("RELAYD_GET_CONFIGURATION", conn);
- ret = relay_get_configuration(header, conn, payload);
- break;
- case RELAYD_UPDATE_SYNC_INFO:
- default:
- ERR("Received unknown command (%u)", header->cmd);
- relay_unknown_command(conn);
- ret = -1;
- goto end;
- }
-
-end:
- return ret;
-}
-
-static enum relay_connection_status relay_process_control_receive_payload(
- struct relay_connection *conn)
-{
- int ret = 0;
- enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
- struct lttng_dynamic_buffer *reception_buffer =
- &conn->protocol.ctrl.reception_buffer;
- struct ctrl_connection_state_receive_payload *state =
- &conn->protocol.ctrl.state.receive_payload;
- struct lttng_buffer_view payload_view;
-
- if (state->left_to_receive == 0) {
- /* Short-circuit for payload-less commands. */
- goto reception_complete;
- }
-
- ret = conn->sock->ops->recvmsg(conn->sock,
- reception_buffer->data + state->received,
- state->left_to_receive, MSG_DONTWAIT);
- if (ret < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- PERROR("Unable to receive command payload on sock %d",
- conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_ERROR;
- }
- goto end;
- } else if (ret == 0) {
- DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_CLOSED;
- goto end;
- }
-
- LTTNG_ASSERT(ret > 0);
- LTTNG_ASSERT(ret <= state->left_to_receive);
-
- state->left_to_receive -= ret;
- state->received += ret;
-
- if (state->left_to_receive > 0) {
- /*
- * Can't transition to the protocol's next state, wait to
- * receive the rest of the header.
- */
- DBG3("Partial reception of control connection protocol payload (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
- state->received, state->left_to_receive,
- conn->sock->fd);
- goto end;
- }
-
-reception_complete:
- DBG("Done receiving control command payload: fd = %i, payload size = %" PRIu64 " bytes",
- conn->sock->fd, state->received);
- /*
- * The payload required to process the command has been received.
- * A view to the reception buffer is forwarded to the various
- * commands and the state of the control is reset on success.
- *
- * Commands are responsible for sending their reply to the peer.
- */
- payload_view = lttng_buffer_view_from_dynamic_buffer(reception_buffer,
- 0, -1);
- ret = relay_process_control_command(conn,
- &state->header, &payload_view);
- if (ret < 0) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end;
- }
-
- ret = connection_reset_protocol_state(conn);
- if (ret) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- }
-end:
- return status;
-}
-
-static enum relay_connection_status relay_process_control_receive_header(
- struct relay_connection *conn)
-{
- int ret = 0;
- enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
- struct lttcomm_relayd_hdr header;
- struct lttng_dynamic_buffer *reception_buffer =
- &conn->protocol.ctrl.reception_buffer;
- struct ctrl_connection_state_receive_header *state =
- &conn->protocol.ctrl.state.receive_header;
-
- LTTNG_ASSERT(state->left_to_receive != 0);
-
- ret = conn->sock->ops->recvmsg(conn->sock,
- reception_buffer->data + state->received,
- state->left_to_receive, MSG_DONTWAIT);
- if (ret < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- PERROR("Unable to receive control command header on sock %d",
- conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_ERROR;
- }
- goto end;
- } else if (ret == 0) {
- DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_CLOSED;
- goto end;
- }
-
- LTTNG_ASSERT(ret > 0);
- LTTNG_ASSERT(ret <= state->left_to_receive);
-
- state->left_to_receive -= ret;
- state->received += ret;
-
- if (state->left_to_receive > 0) {
- /*
- * Can't transition to the protocol's next state, wait to
- * receive the rest of the header.
- */
- DBG3("Partial reception of control connection protocol header (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
- state->received, state->left_to_receive,
- conn->sock->fd);
- goto end;
- }
-
- /* Transition to next state: receiving the command's payload. */
- conn->protocol.ctrl.state_id =
- CTRL_CONNECTION_STATE_RECEIVE_PAYLOAD;
- memcpy(&header, reception_buffer->data, sizeof(header));
- header.circuit_id = be64toh(header.circuit_id);
- header.data_size = be64toh(header.data_size);
- header.cmd = be32toh(header.cmd);
- header.cmd_version = be32toh(header.cmd_version);
- memcpy(&conn->protocol.ctrl.state.receive_payload.header,
- &header, sizeof(header));
-
- DBG("Done receiving control command header: fd = %i, cmd = %" PRIu32 ", cmd_version = %" PRIu32 ", payload size = %" PRIu64 " bytes",
- conn->sock->fd, header.cmd, header.cmd_version,
- header.data_size);
-
- if (header.data_size > DEFAULT_NETWORK_RELAYD_CTRL_MAX_PAYLOAD_SIZE) {
- ERR("Command header indicates a payload (%" PRIu64 " bytes) that exceeds the maximal payload size allowed on a control connection.",
- header.data_size);
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end;
- }
-
- conn->protocol.ctrl.state.receive_payload.left_to_receive =
- header.data_size;
- conn->protocol.ctrl.state.receive_payload.received = 0;
- ret = lttng_dynamic_buffer_set_size(reception_buffer,
- header.data_size);
- if (ret) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end;
- }
-
- if (header.data_size == 0) {
- /*
- * Manually invoke the next state as the poll loop
- * will not wake-up to allow us to proceed further.
- */
- status = relay_process_control_receive_payload(conn);
- }
-end:
- return status;
-}
-
-/*
- * Process the commands received on the control socket
- */
-static enum relay_connection_status relay_process_control(
- struct relay_connection *conn)
-{
- enum relay_connection_status status;
-
- switch (conn->protocol.ctrl.state_id) {
- case CTRL_CONNECTION_STATE_RECEIVE_HEADER:
- status = relay_process_control_receive_header(conn);
- break;
- case CTRL_CONNECTION_STATE_RECEIVE_PAYLOAD:
- status = relay_process_control_receive_payload(conn);
- break;
- default:
- ERR("Unknown control connection protocol state encountered.");
- abort();
- }
-
- return status;
-}
-
-static enum relay_connection_status relay_process_data_receive_header(
- struct relay_connection *conn)
-{
- int ret;
- enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
- struct data_connection_state_receive_header *state =
- &conn->protocol.data.state.receive_header;
- struct lttcomm_relayd_data_hdr header;
- struct relay_stream *stream;
-
- LTTNG_ASSERT(state->left_to_receive != 0);
-
- ret = conn->sock->ops->recvmsg(conn->sock,
- state->header_reception_buffer + state->received,
- state->left_to_receive, MSG_DONTWAIT);
- if (ret < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- PERROR("Unable to receive data header on sock %d", conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_ERROR;
- }
- goto end;
- } else if (ret == 0) {
- /* Orderly shutdown. Not necessary to print an error. */
- DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_CLOSED;
- goto end;
- }
-
- LTTNG_ASSERT(ret > 0);
- LTTNG_ASSERT(ret <= state->left_to_receive);
-
- state->left_to_receive -= ret;
- state->received += ret;
-
- if (state->left_to_receive > 0) {
- /*
- * Can't transition to the protocol's next state, wait to
- * receive the rest of the header.
- */
- DBG3("Partial reception of data connection header (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
- state->received, state->left_to_receive,
- conn->sock->fd);
- goto end;
- }
-
- /* Transition to next state: receiving the payload. */
- conn->protocol.data.state_id = DATA_CONNECTION_STATE_RECEIVE_PAYLOAD;
-
- memcpy(&header, state->header_reception_buffer, sizeof(header));
- header.circuit_id = be64toh(header.circuit_id);
- header.stream_id = be64toh(header.stream_id);
- header.data_size = be32toh(header.data_size);
- header.net_seq_num = be64toh(header.net_seq_num);
- header.padding_size = be32toh(header.padding_size);
- memcpy(&conn->protocol.data.state.receive_payload.header, &header, sizeof(header));
-
- conn->protocol.data.state.receive_payload.left_to_receive =
- header.data_size;
- conn->protocol.data.state.receive_payload.received = 0;
- conn->protocol.data.state.receive_payload.rotate_index = false;
-
- DBG("Received data connection header on fd %i: circuit_id = %" PRIu64 ", stream_id = %" PRIu64 ", data_size = %" PRIu32 ", net_seq_num = %" PRIu64 ", padding_size = %" PRIu32,
- conn->sock->fd, header.circuit_id,
- header.stream_id, header.data_size,
- header.net_seq_num, header.padding_size);
-
- stream = stream_get_by_id(header.stream_id);
- if (!stream) {
- DBG("relay_process_data_receive_payload: Cannot find stream %" PRIu64,
- header.stream_id);
- /* Protocol error. */
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
- /* Prepare stream for the reception of a new packet. */
- ret = stream_init_packet(stream, header.data_size,
- &conn->protocol.data.state.receive_payload.rotate_index);
- pthread_mutex_unlock(&stream->lock);
- if (ret) {
- ERR("Failed to rotate stream output file");
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
-
-end_stream_unlock:
- stream_put(stream);
-end:
- return status;
-}
-
-static enum relay_connection_status relay_process_data_receive_payload(
- struct relay_connection *conn)
-{
- int ret;
- enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
- struct relay_stream *stream;
- struct data_connection_state_receive_payload *state =
- &conn->protocol.data.state.receive_payload;
- const size_t chunk_size = RECV_DATA_BUFFER_SIZE;
- char data_buffer[chunk_size];
- bool partial_recv = false;
- bool new_stream = false, close_requested = false, index_flushed = false;
- uint64_t left_to_receive = state->left_to_receive;
- struct relay_session *session;
-
- DBG3("Receiving data for stream id %" PRIu64 " seqnum %" PRIu64 ", %" PRIu64" bytes received, %" PRIu64 " bytes left to receive",
- state->header.stream_id, state->header.net_seq_num,
- state->received, left_to_receive);
-
- stream = stream_get_by_id(state->header.stream_id);
- if (!stream) {
- /* Protocol error. */
- ERR("relay_process_data_receive_payload: cannot find stream %" PRIu64,
- state->header.stream_id);
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
- session = stream->trace->session;
- if (!conn->session) {
- ret = connection_set_session(conn, session);
- if (ret) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
- }
-
- /*
- * The size of the "chunk" received on any iteration is bounded by:
- * - the data left to receive,
- * - the data immediately available on the socket,
- * - the on-stack data buffer
- */
- while (left_to_receive > 0 && !partial_recv) {
- size_t recv_size = min(left_to_receive, chunk_size);
- struct lttng_buffer_view packet_chunk;
-
- ret = conn->sock->ops->recvmsg(conn->sock, data_buffer,
- recv_size, MSG_DONTWAIT);
- if (ret < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- PERROR("Socket %d error", conn->sock->fd);
- status = RELAY_CONNECTION_STATUS_ERROR;
- }
- goto end_stream_unlock;
- } else if (ret == 0) {
- /* No more data ready to be consumed on socket. */
- DBG3("No more data ready for consumption on data socket of stream id %" PRIu64,
- state->header.stream_id);
- status = RELAY_CONNECTION_STATUS_CLOSED;
- break;
- } else if (ret < (int) recv_size) {
- /*
- * All the data available on the socket has been
- * consumed.
- */
- partial_recv = true;
- recv_size = ret;
- }
-
- packet_chunk = lttng_buffer_view_init(data_buffer,
- 0, recv_size);
- LTTNG_ASSERT(packet_chunk.data);
-
- ret = stream_write(stream, &packet_chunk, 0);
- if (ret) {
- ERR("Relay error writing data to file");
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
-
- left_to_receive -= recv_size;
- state->received += recv_size;
- state->left_to_receive = left_to_receive;
- }
-
- if (state->left_to_receive > 0) {
- /*
- * Did not receive all the data expected, wait for more data to
- * become available on the socket.
- */
- DBG3("Partial receive on data connection of stream id %" PRIu64 ", %" PRIu64 " bytes received, %" PRIu64 " bytes left to receive",
- state->header.stream_id, state->received,
- state->left_to_receive);
- goto end_stream_unlock;
- }
-
- ret = stream_write(stream, NULL, state->header.padding_size);
- if (ret) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
-
- if (session_streams_have_index(session)) {
- ret = stream_update_index(stream, state->header.net_seq_num,
- state->rotate_index, &index_flushed,
- state->header.data_size + state->header.padding_size);
- if (ret < 0) {
- ERR("Failed to update index: stream %" PRIu64 " net_seq_num %" PRIu64 " ret %d",
- stream->stream_handle,
- state->header.net_seq_num, ret);
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
- }
-
- if (stream->prev_data_seq == -1ULL) {
- new_stream = true;
- }
-
- ret = stream_complete_packet(stream, state->header.data_size +
- state->header.padding_size, state->header.net_seq_num,
- index_flushed);
- if (ret) {
- status = RELAY_CONNECTION_STATUS_ERROR;
- goto end_stream_unlock;
- }
-
- /*
- * Resetting the protocol state (to RECEIVE_HEADER) will trash the
- * contents of *state which are aliased (union) to the same location as
- * the new state. Don't use it beyond this point.
- */
- connection_reset_protocol_state(conn);
- state = NULL;
-
-end_stream_unlock:
- close_requested = stream->close_requested;
- pthread_mutex_unlock(&stream->lock);
- if (close_requested && left_to_receive == 0) {
- try_stream_close(stream);
- }
-
- if (new_stream) {
- pthread_mutex_lock(&session->lock);
- uatomic_set(&session->new_streams, 1);
- pthread_mutex_unlock(&session->lock);
- }
-
- stream_put(stream);
-end:
- return status;
-}
-
-/*
- * relay_process_data: Process the data received on the data socket
- */
-static enum relay_connection_status relay_process_data(
- struct relay_connection *conn)
-{
- enum relay_connection_status status;
-
- switch (conn->protocol.data.state_id) {
- case DATA_CONNECTION_STATE_RECEIVE_HEADER:
- status = relay_process_data_receive_header(conn);
- break;
- case DATA_CONNECTION_STATE_RECEIVE_PAYLOAD:
- status = relay_process_data_receive_payload(conn);
- break;
- default:
- ERR("Unexpected data connection communication state.");
- abort();
- }
-
- return status;
-}
-
-static void cleanup_connection_pollfd(struct lttng_poll_event *events, int pollfd)
-{
- int ret;
-
- (void) lttng_poll_del(events, pollfd);
-
- ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &pollfd, 1,
- fd_tracker_util_close_fd, NULL);
- if (ret < 0) {
- ERR("Closing pollfd %d", pollfd);
- }
-}
-
-static void relay_thread_close_connection(struct lttng_poll_event *events,
- int pollfd, struct relay_connection *conn)
-{
- const char *type_str;
-
- switch (conn->type) {
- case RELAY_DATA:
- type_str = "Data";
- break;
- case RELAY_CONTROL:
- type_str = "Control";
- break;
- case RELAY_VIEWER_COMMAND:
- type_str = "Viewer Command";
- break;
- case RELAY_VIEWER_NOTIFICATION:
- type_str = "Viewer Notification";
- break;
- default:
- type_str = "Unknown";
- }
- cleanup_connection_pollfd(events, pollfd);
- connection_put(conn);
- DBG("%s connection closed with %d", type_str, pollfd);
-}
-
-/*
- * This thread does the actual work
- */
-static void *relay_thread_worker(void *data)
-{
- int ret, err = -1, last_seen_data_fd = -1;
- uint32_t nb_fd;
- struct lttng_poll_event events;
- struct lttng_ht *relay_connections_ht;
- struct lttng_ht_iter iter;
- struct relay_connection *destroy_conn = NULL;
-
- DBG("[thread] Relay worker started");
-
- rcu_register_thread();
-
- health_register(health_relayd, HEALTH_RELAYD_TYPE_WORKER);
-
- if (testpoint(relayd_thread_worker)) {
- goto error_testpoint;
- }
-
- health_code_update();
-
- /* table of connections indexed on socket */
- relay_connections_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG);
- if (!relay_connections_ht) {
- goto relay_connections_ht_error;
- }
-
- ret = create_named_thread_poll_set(&events, 2, "Worker thread epoll");
- if (ret < 0) {
- goto error_poll_create;
- }
-
- ret = lttng_poll_add(&events, relay_conn_pipe[0], LPOLLIN | LPOLLRDHUP);
- if (ret < 0) {
- goto error;
- }
-
-restart:
- while (1) {
- int idx = -1, i, seen_control = 0, last_notdel_data_fd = -1;
-
- health_code_update();
-
- /* Infinite blocking call, waiting for transmission */
- DBG3("Relayd worker thread polling...");
- health_poll_entry();
- ret = lttng_poll_wait(&events, -1);
- health_poll_exit();
- if (ret < 0) {
- /*
- * Restart interrupted system call.
- */
- if (errno == EINTR) {
- goto restart;
- }
- goto error;
- }
-
- nb_fd = ret;
-
- /*
- * Process control. The control connection is
- * prioritized so we don't starve it with high
- * throughput tracing data on the data connection.
- */
- for (i = 0; i < nb_fd; i++) {
- /* Fetch once the poll data */
- uint32_t revents = LTTNG_POLL_GETEV(&events, i);
- int pollfd = LTTNG_POLL_GETFD(&events, i);
-
- health_code_update();
-
- /* Thread quit pipe has been closed. Killing thread. */
- ret = check_thread_quit_pipe(pollfd, revents);
- if (ret) {
- err = 0;
- goto exit;
- }
-
- /* Inspect the relay conn pipe for new connection */
- if (pollfd == relay_conn_pipe[0]) {
- if (revents & LPOLLIN) {
- struct relay_connection *conn;
-
- ret = lttng_read(relay_conn_pipe[0], &conn, sizeof(conn));
- if (ret < 0) {
- goto error;
- }
- ret = lttng_poll_add(&events,
- conn->sock->fd,
- LPOLLIN | LPOLLRDHUP);
- if (ret) {
- ERR("Failed to add new connection file descriptor to poll set");
- goto error;
- }
- connection_ht_add(relay_connections_ht, conn);
- DBG("Connection socket %d added", conn->sock->fd);
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- ERR("Relay connection pipe error");
- goto error;
- } else {
- ERR("Unexpected poll events %u for sock %d", revents, pollfd);
- goto error;
- }
- } else {
- struct relay_connection *ctrl_conn;
-
- ctrl_conn = connection_get_by_sock(relay_connections_ht, pollfd);
- /* If not found, there is a synchronization issue. */
- LTTNG_ASSERT(ctrl_conn);
-
- if (ctrl_conn->type == RELAY_DATA) {
- if (revents & LPOLLIN) {
- /*
- * Flag the last seen data fd not deleted. It will be
- * used as the last seen fd if any fd gets deleted in
- * this first loop.
- */
- last_notdel_data_fd = pollfd;
- }
- goto put_ctrl_connection;
- }
- LTTNG_ASSERT(ctrl_conn->type == RELAY_CONTROL);
-
- if (revents & LPOLLIN) {
- enum relay_connection_status status;
-
- status = relay_process_control(ctrl_conn);
- if (status != RELAY_CONNECTION_STATUS_OK) {
- /*
- * On socket error flag the session as aborted to force
- * the cleanup of its stream otherwise it can leak
- * during the lifetime of the relayd.
- *
- * This prevents situations in which streams can be
- * left opened because an index was received, the
- * control connection is closed, and the data
- * connection is closed (uncleanly) before the packet's
- * data provided.
- *
- * Since the control connection encountered an error,
- * it is okay to be conservative and close the
- * session right now as we can't rely on the protocol
- * being respected anymore.
- */
- if (status == RELAY_CONNECTION_STATUS_ERROR) {
- session_abort(ctrl_conn->session);
- }
-
- /* Clear the connection on error or close. */
- relay_thread_close_connection(&events,
- pollfd,
- ctrl_conn);
- }
- seen_control = 1;
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- relay_thread_close_connection(&events,
- pollfd, ctrl_conn);
- if (last_seen_data_fd == pollfd) {
- last_seen_data_fd = last_notdel_data_fd;
- }
- } else {
- ERR("Unexpected poll events %u for control sock %d",
- revents, pollfd);
- connection_put(ctrl_conn);
- goto error;
- }
- put_ctrl_connection:
- connection_put(ctrl_conn);
- }
- }
-
- /*
- * The last loop handled a control request, go back to poll to make
- * sure we prioritise the control socket.
- */
- if (seen_control) {
- continue;
- }
-
- if (last_seen_data_fd >= 0) {
- for (i = 0; i < nb_fd; i++) {
- int pollfd = LTTNG_POLL_GETFD(&events, i);
-
- health_code_update();
-
- if (last_seen_data_fd == pollfd) {
- idx = i;
- break;
- }
- }
- }
-
- /* Process data connection. */
- for (i = idx + 1; i < nb_fd; i++) {
- /* Fetch the poll data. */
- uint32_t revents = LTTNG_POLL_GETEV(&events, i);
- int pollfd = LTTNG_POLL_GETFD(&events, i);
- struct relay_connection *data_conn;
-
- health_code_update();
-
- if (!revents) {
- /* No activity for this FD (poll implementation). */
- continue;
- }
-
- /* Skip the command pipe. It's handled in the first loop. */
- if (pollfd == relay_conn_pipe[0]) {
- continue;
- }
-
- data_conn = connection_get_by_sock(relay_connections_ht, pollfd);
- if (!data_conn) {
- /* Skip it. Might be removed before. */
- continue;
- }
- if (data_conn->type == RELAY_CONTROL) {
- goto put_data_connection;
- }
- LTTNG_ASSERT(data_conn->type == RELAY_DATA);
-
- if (revents & LPOLLIN) {
- enum relay_connection_status status;
-
- status = relay_process_data(data_conn);
- /* Connection closed or error. */
- if (status != RELAY_CONNECTION_STATUS_OK) {
- /*
- * On socket error flag the session as aborted to force
- * the cleanup of its stream otherwise it can leak
- * during the lifetime of the relayd.
- *
- * This prevents situations in which streams can be
- * left opened because an index was received, the
- * control connection is closed, and the data
- * connection is closed (uncleanly) before the packet's
- * data provided.
- *
- * Since the data connection encountered an error,
- * it is okay to be conservative and close the
- * session right now as we can't rely on the protocol
- * being respected anymore.
- */
- if (status == RELAY_CONNECTION_STATUS_ERROR) {
- session_abort(data_conn->session);
- }
- relay_thread_close_connection(&events, pollfd,
- data_conn);
- /*
- * Every goto restart call sets the last seen fd where
- * here we don't really care since we gracefully
- * continue the loop after the connection is deleted.
- */
- } else {
- /* Keep last seen port. */
- last_seen_data_fd = pollfd;
- connection_put(data_conn);
- goto restart;
- }
- } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
- relay_thread_close_connection(&events, pollfd,
- data_conn);
- } else {
- ERR("Unknown poll events %u for data sock %d",
- revents, pollfd);
- }
- put_data_connection:
- connection_put(data_conn);
- }
- last_seen_data_fd = -1;
- }
-
- /* Normal exit, no error */
- ret = 0;
-
-exit:
-error:
- /* Cleanup remaining connection object. */
- rcu_read_lock();
- cds_lfht_for_each_entry(relay_connections_ht->ht, &iter.iter,
- destroy_conn,
- sock_n.node) {
- health_code_update();
-
- session_abort(destroy_conn->session);
-
- /*
- * No need to grab another ref, because we own
- * destroy_conn.
- */
- relay_thread_close_connection(&events, destroy_conn->sock->fd,
- destroy_conn);
- }
- rcu_read_unlock();
-
- (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
-error_poll_create:
- lttng_ht_destroy(relay_connections_ht);
-relay_connections_ht_error:
- /* Close relay conn pipes */
- (void) fd_tracker_util_pipe_close(the_fd_tracker,
- relay_conn_pipe);
- if (err) {
- DBG("Thread exited with error");
- }
- DBG("Worker thread cleanup complete");
-error_testpoint:
- if (err) {
- health_error();
- ERR("Health error occurred in %s", __func__);
- }
- health_unregister(health_relayd);
- rcu_unregister_thread();
- lttng_relay_stop_threads();
- return NULL;
-}
-
-/*
- * Create the relay command pipe to wake thread_manage_apps.
- * Closed in cleanup().
- */
-static int create_relay_conn_pipe(void)
-{
- return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
- "Relayd connection pipe", relay_conn_pipe);
-}
-
-static int stdio_open(void *data, int *fds)
-{
- fds[0] = fileno(stdout);
- fds[1] = fileno(stderr);
- return 0;
-}
-
-static int track_stdio(void)
-{
- int fds[2];
- const char *names[] = { "stdout", "stderr" };
-
- return fd_tracker_open_unsuspendable_fd(the_fd_tracker, fds,
- names, 2, stdio_open, NULL);
-}
-
-/*
- * main
- */
-int main(int argc, char **argv)
-{
- bool thread_is_rcu_registered = false;
- int ret = 0, retval = 0;
- void *status;
- char *unlinked_file_directory_path = NULL, *output_path = NULL;
-
- /* Parse environment variables */
- ret = parse_env_options();
- if (ret) {
- retval = -1;
- goto exit_options;
- }
-
- /*
- * Parse arguments.
- * Command line arguments overwrite environment.
- */
- progname = argv[0];
- if (set_options(argc, argv)) {
- retval = -1;
- goto exit_options;
- }
-
- if (set_signal_handler()) {
- retval = -1;
- goto exit_options;
- }
-
- relayd_config_log();
-
- if (opt_print_version) {
- print_version();
- retval = 0;
- goto exit_options;
- }
-
- ret = fclose(stdin);
- if (ret) {
- PERROR("Failed to close stdin");
- goto exit_options;
- }
-
- DBG("Clear command %s", opt_allow_clear ? "allowed" : "disallowed");
-
- /* Try to create directory if -o, --output is specified. */
- if (opt_output_path) {
- if (*opt_output_path != '/') {
- ERR("Please specify an absolute path for -o, --output PATH");
- retval = -1;
- goto exit_options;
- }
-
- ret = utils_mkdir_recursive(opt_output_path, S_IRWXU | S_IRWXG,
- -1, -1);
- if (ret < 0) {
- ERR("Unable to create %s", opt_output_path);
- retval = -1;
- goto exit_options;
- }
- }
-
- /* Daemonize */
- if (opt_daemon || opt_background) {
- ret = lttng_daemonize(&child_ppid, &recv_child_signal,
- !opt_background);
- if (ret < 0) {
- retval = -1;
- goto exit_options;
- }
- }
-
- if (opt_working_directory) {
- ret = utils_change_working_directory(opt_working_directory);
- if (ret) {
- /* All errors are already logged. */
- goto exit_options;
- }
- }
-
- sessiond_trace_chunk_registry = sessiond_trace_chunk_registry_create();
- if (!sessiond_trace_chunk_registry) {
- ERR("Failed to initialize session daemon trace chunk registry");
- retval = -1;
- goto exit_options;
- }
-
- /*
- * The RCU thread registration (and use, through the fd-tracker's
- * creation) is done after the daemonization to allow us to not
- * deal with liburcu's fork() management as the call RCU needs to
- * be restored.
- */
- rcu_register_thread();
- thread_is_rcu_registered = true;
-
- output_path = create_output_path("");
- if (!output_path) {
- ERR("Failed to get output path");
- retval = -1;
- goto exit_options;
- }
- ret = asprintf(&unlinked_file_directory_path, "%s/%s", output_path,
- DEFAULT_UNLINKED_FILES_DIRECTORY);
- free(output_path);
- if (ret < 0) {
- ERR("Failed to format unlinked file directory path");
- retval = -1;
- goto exit_options;
- }
- the_fd_tracker = fd_tracker_create(
- unlinked_file_directory_path, lttng_opt_fd_pool_size);
- free(unlinked_file_directory_path);
- if (!the_fd_tracker) {
- retval = -1;
- goto exit_options;
- }
-
- ret = track_stdio();
- if (ret) {
- retval = -1;
- goto exit_options;
- }
-
- /* Initialize thread health monitoring */
- health_relayd = health_app_create(NR_HEALTH_RELAYD_TYPES);
- if (!health_relayd) {
- PERROR("health_app_create error");
- retval = -1;
- goto exit_options;
- }
-
- /* Create thread quit pipe */
- if (init_thread_quit_pipe()) {
- retval = -1;
- goto exit_options;
- }
-
- /* Setup the thread apps communication pipe. */
- if (create_relay_conn_pipe()) {
- retval = -1;
- goto exit_options;
- }
-
- /* Init relay command queue. */
- cds_wfcq_init(&relay_conn_queue.head, &relay_conn_queue.tail);
-
- /* Initialize communication library */
- lttcomm_init();
- lttcomm_inet_init();
-
- /* tables of sessions indexed by session ID */
- sessions_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
- if (!sessions_ht) {
- retval = -1;
- goto exit_options;
- }
-
- /* tables of streams indexed by stream ID */
- relay_streams_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
- if (!relay_streams_ht) {
- retval = -1;
- goto exit_options;
- }
-
- /* tables of streams indexed by stream ID */
- viewer_streams_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
- if (!viewer_streams_ht) {
- retval = -1;
- goto exit_options;
- }
-
- ret = init_health_quit_pipe();
- if (ret) {
- retval = -1;
- goto exit_options;
- }
-
- /* Create thread to manage the client socket */
- ret = pthread_create(&health_thread, default_pthread_attr(),
- thread_manage_health, (void *) NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create health");
- retval = -1;
- goto exit_options;
- }
-
- /* Setup the dispatcher thread */
- ret = pthread_create(&dispatcher_thread, default_pthread_attr(),
- relay_thread_dispatcher, (void *) NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create dispatcher");
- retval = -1;
- goto exit_dispatcher_thread;
- }
-
- /* Setup the worker thread */
- ret = pthread_create(&worker_thread, default_pthread_attr(),
- relay_thread_worker, NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create worker");
- retval = -1;
- goto exit_worker_thread;
- }
-
- /* Setup the listener thread */
- ret = pthread_create(&listener_thread, default_pthread_attr(),
- relay_thread_listener, (void *) NULL);
- if (ret) {
- errno = ret;
- PERROR("pthread_create listener");
- retval = -1;
- goto exit_listener_thread;
- }
-
- ret = relayd_live_create(live_uri);
- if (ret) {
- ERR("Starting live viewer threads");
- retval = -1;
- goto exit_live;
- }
-
- /*
- * This is where we start awaiting program completion (e.g. through
- * signal that asks threads to teardown).
- */
-
- ret = relayd_live_join();
- if (ret) {
- retval = -1;
- }
-exit_live:
-
- ret = pthread_join(listener_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join listener_thread");
- retval = -1;
- }
-
-exit_listener_thread:
- ret = pthread_join(worker_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join worker_thread");
- retval = -1;
- }
-
-exit_worker_thread:
- ret = pthread_join(dispatcher_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join dispatcher_thread");
- retval = -1;
- }
-exit_dispatcher_thread:
-
- ret = pthread_join(health_thread, &status);
- if (ret) {
- errno = ret;
- PERROR("pthread_join health_thread");
- retval = -1;
- }
-exit_options:
- /*
- * Wait for all pending call_rcu work to complete before tearing
- * down data structures. call_rcu worker may be trying to
- * perform lookups in those structures.
- */
- rcu_barrier();
- relayd_cleanup();
-
- /* Ensure all prior call_rcu are done. */
- rcu_barrier();
-
- if (thread_is_rcu_registered) {
- rcu_unregister_thread();
- }
-
- if (!retval) {
- exit(EXIT_SUCCESS);
- } else {
- exit(EXIT_FAILURE);
- }
-}
--- /dev/null
+/*
+ * Copyright (C) 2012 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2012 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <getopt.h>
+#include <grp.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <inttypes.h>
+#include <urcu/futex.h>
+#include <urcu/uatomic.h>
+#include <urcu/rculist.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <ctype.h>
+#include <algorithm>
+
+#include <lttng/lttng.h>
+#include <common/common.h>
+#include <common/compat/poll.h>
+#include <common/compat/socket.h>
+#include <common/compat/endian.h>
+#include <common/compat/getenv.h>
+#include <common/defaults.h>
+#include <common/daemonize.h>
+#include <common/futex.h>
+#include <common/sessiond-comm/sessiond-comm.h>
+#include <common/sessiond-comm/inet.h>
+#include <common/sessiond-comm/relayd.h>
+#include <common/uri.h>
+#include <common/utils.h>
+#include <common/align.h>
+#include <common/config/session-config.h>
+#include <common/dynamic-buffer.h>
+#include <common/buffer-view.h>
+#include <common/string-utils/format.h>
+#include <common/fd-tracker/fd-tracker.h>
+#include <common/fd-tracker/utils.h>
+
+#include "backward-compatibility-group-by.h"
+#include "cmd.h"
+#include "connection.h"
+#include "ctf-trace.h"
+#include "health-relayd.h"
+#include "index.h"
+#include "live.h"
+#include "lttng-relayd.h"
+#include "session.h"
+#include "sessiond-trace-chunks.h"
+#include "stream.h"
+#include "tcp_keep_alive.h"
+#include "testpoint.h"
+#include "tracefile-array.h"
+#include "utils.h"
+#include "version.h"
+#include "viewer-stream.h"
+
+static const char *help_msg =
+#ifdef LTTNG_EMBED_HELP
+#include <lttng-relayd.8.h>
+#else
+NULL
+#endif
+;
+
+enum relay_connection_status {
+ RELAY_CONNECTION_STATUS_OK,
+ /* An error occurred while processing an event on the connection. */
+ RELAY_CONNECTION_STATUS_ERROR,
+ /* Connection closed/shutdown cleanly. */
+ RELAY_CONNECTION_STATUS_CLOSED,
+};
+
+/* command line options */
+char *opt_output_path, *opt_working_directory;
+static int opt_daemon, opt_background, opt_print_version, opt_allow_clear = 1;
+enum relay_group_output_by opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_UNKNOWN;
+
+/*
+ * We need to wait for listener and live listener threads, as well as
+ * health check thread, before being ready to signal readiness.
+ */
+#define NR_LTTNG_RELAY_READY 3
+static int lttng_relay_ready = NR_LTTNG_RELAY_READY;
+
+/* Size of receive buffer. */
+#define RECV_DATA_BUFFER_SIZE 65536
+
+static int recv_child_signal; /* Set to 1 when a SIGUSR1 signal is received. */
+static pid_t child_ppid; /* Internal parent PID use with daemonize. */
+
+static struct lttng_uri *control_uri;
+static struct lttng_uri *data_uri;
+static struct lttng_uri *live_uri;
+
+const char *progname;
+
+const char *tracing_group_name = DEFAULT_TRACING_GROUP;
+static int tracing_group_name_override;
+
+const char * const config_section_name = "relayd";
+
+/*
+ * Quit pipe for all threads. This permits a single cancellation point
+ * for all threads when receiving an event on the pipe.
+ */
+int thread_quit_pipe[2] = { -1, -1 };
+
+/*
+ * This pipe is used to inform the worker thread that a command is queued and
+ * ready to be processed.
+ */
+static int relay_conn_pipe[2] = { -1, -1 };
+
+/* Shared between threads */
+static int dispatch_thread_exit;
+
+static pthread_t listener_thread;
+static pthread_t dispatcher_thread;
+static pthread_t worker_thread;
+static pthread_t health_thread;
+
+/*
+ * last_relay_stream_id_lock protects last_relay_stream_id increment
+ * atomicity on 32-bit architectures.
+ */
+static pthread_mutex_t last_relay_stream_id_lock = PTHREAD_MUTEX_INITIALIZER;
+static uint64_t last_relay_stream_id;
+
+/*
+ * Relay command queue.
+ *
+ * The relay_thread_listener and relay_thread_dispatcher communicate with this
+ * queue.
+ */
+static struct relay_conn_queue relay_conn_queue;
+
+/* Cap of file desriptors to be in simultaneous use by the relay daemon. */
+static unsigned int lttng_opt_fd_pool_size = -1;
+
+/* Global relay stream hash table. */
+struct lttng_ht *relay_streams_ht;
+
+/* Global relay viewer stream hash table. */
+struct lttng_ht *viewer_streams_ht;
+
+/* Global relay sessions hash table. */
+struct lttng_ht *sessions_ht;
+
+/* Relayd health monitoring */
+struct health_app *health_relayd;
+
+struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry;
+
+/* Global fd tracker. */
+struct fd_tracker *the_fd_tracker;
+
+static struct option long_options[] = {
+ { "control-port", 1, 0, 'C', },
+ { "data-port", 1, 0, 'D', },
+ { "live-port", 1, 0, 'L', },
+ { "daemonize", 0, 0, 'd', },
+ { "background", 0, 0, 'b', },
+ { "group", 1, 0, 'g', },
+ { "fd-pool-size", 1, 0, '\0', },
+ { "help", 0, 0, 'h', },
+ { "output", 1, 0, 'o', },
+ { "verbose", 0, 0, 'v', },
+ { "config", 1, 0, 'f' },
+ { "version", 0, 0, 'V' },
+ { "working-directory", 1, 0, 'w', },
+ { "group-output-by-session", 0, 0, 's', },
+ { "group-output-by-host", 0, 0, 'p', },
+ { "disallow-clear", 0, 0, 'x' },
+ { NULL, 0, 0, 0, },
+};
+
+static const char *config_ignore_options[] = { "help", "config", "version" };
+
+static void print_version(void) {
+ fprintf(stdout, "%s\n", VERSION);
+}
+
+static void relayd_config_log(void)
+{
+ DBG("LTTng-relayd " VERSION " - " VERSION_NAME "%s%s",
+ GIT_VERSION[0] == '\0' ? "" : " - " GIT_VERSION,
+ EXTRA_VERSION_NAME[0] == '\0' ? "" : " - " EXTRA_VERSION_NAME);
+ if (EXTRA_VERSION_DESCRIPTION[0] != '\0') {
+ DBG("LTTng-relayd extra version description:\n\t" EXTRA_VERSION_DESCRIPTION "\n");
+ }
+ if (EXTRA_VERSION_PATCHES[0] != '\0') {
+ DBG("LTTng-relayd extra patches:\n\t" EXTRA_VERSION_PATCHES "\n");
+ }
+}
+
+/*
+ * Take an option from the getopt output and set it in the right variable to be
+ * used later.
+ *
+ * Return 0 on success else a negative value.
+ */
+static int set_option(int opt, const char *arg, const char *optname)
+{
+ int ret;
+
+ switch (opt) {
+ case 0:
+ if (!strcmp(optname, "fd-pool-size")) {
+ unsigned long v;
+
+ errno = 0;
+ v = strtoul(arg, NULL, 0);
+ if (errno != 0 || !isdigit((unsigned char) arg[0])) {
+ ERR("Wrong value in --fd-pool-size parameter: %s", arg);
+ ret = -1;
+ goto end;
+ }
+ if (v >= UINT_MAX) {
+ ERR("File descriptor cap overflow in --fd-pool-size parameter: %s", arg);
+ ret = -1;
+ goto end;
+ }
+ lttng_opt_fd_pool_size = (unsigned int) v;
+ } else {
+ fprintf(stderr, "unknown option %s", optname);
+ if (arg) {
+ fprintf(stderr, " with arg %s\n", arg);
+ }
+ }
+ break;
+ case 'C':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-C, --control-port");
+ } else {
+ ret = uri_parse(arg, &control_uri);
+ if (ret < 0) {
+ ERR("Invalid control URI specified");
+ goto end;
+ }
+ if (control_uri->port == 0) {
+ control_uri->port = DEFAULT_NETWORK_CONTROL_PORT;
+ }
+ }
+ break;
+ case 'D':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-D, -data-port");
+ } else {
+ ret = uri_parse(arg, &data_uri);
+ if (ret < 0) {
+ ERR("Invalid data URI specified");
+ goto end;
+ }
+ if (data_uri->port == 0) {
+ data_uri->port = DEFAULT_NETWORK_DATA_PORT;
+ }
+ }
+ break;
+ case 'L':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-L, -live-port");
+ } else {
+ ret = uri_parse(arg, &live_uri);
+ if (ret < 0) {
+ ERR("Invalid live URI specified");
+ goto end;
+ }
+ if (live_uri->port == 0) {
+ live_uri->port = DEFAULT_NETWORK_VIEWER_PORT;
+ }
+ }
+ break;
+ case 'd':
+ opt_daemon = 1;
+ break;
+ case 'b':
+ opt_background = 1;
+ break;
+ case 'g':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-g, --group");
+ } else {
+ tracing_group_name = strdup(arg);
+ if (tracing_group_name == NULL) {
+ ret = -errno;
+ PERROR("strdup");
+ goto end;
+ }
+ tracing_group_name_override = 1;
+ }
+ break;
+ case 'h':
+ ret = utils_show_help(8, "lttng-relayd", help_msg);
+ if (ret) {
+ ERR("Cannot show --help for `lttng-relayd`");
+ perror("exec");
+ }
+ exit(EXIT_FAILURE);
+ case 'V':
+ opt_print_version = 1;
+ break;
+ case 'o':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-o, --output");
+ } else {
+ ret = asprintf(&opt_output_path, "%s", arg);
+ if (ret < 0) {
+ ret = -errno;
+ PERROR("asprintf opt_output_path");
+ goto end;
+ }
+ }
+ break;
+ case 'w':
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-w, --working-directory");
+ } else {
+ ret = asprintf(&opt_working_directory, "%s", arg);
+ if (ret < 0) {
+ ret = -errno;
+ PERROR("asprintf opt_working_directory");
+ goto end;
+ }
+ }
+ break;
+
+ case 'v':
+ /* Verbose level can increase using multiple -v */
+ if (arg) {
+ lttng_opt_verbose = config_parse_value(arg);
+ } else {
+ /* Only 3 level of verbosity (-vvv). */
+ if (lttng_opt_verbose < 3) {
+ lttng_opt_verbose += 1;
+ }
+ }
+ break;
+ case 's':
+ if (opt_group_output_by != RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
+ ERR("Cannot set --group-output-by-session, another --group-output-by argument is present");
+ exit(EXIT_FAILURE);
+ }
+ opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_SESSION;
+ break;
+ case 'p':
+ if (opt_group_output_by != RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
+ ERR("Cannot set --group-output-by-host, another --group-output-by argument is present");
+ exit(EXIT_FAILURE);
+ }
+ opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_HOST;
+ break;
+ case 'x':
+ /* Disallow clear */
+ opt_allow_clear = 0;
+ break;
+ default:
+ /* Unknown option or other error.
+ * Error is printed by getopt, just return */
+ ret = -1;
+ goto end;
+ }
+
+ /* All good. */
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/*
+ * config_entry_handler_cb used to handle options read from a config file.
+ * See config_entry_handler_cb comment in common/config/session-config.h for the
+ * return value conventions.
+ */
+static int config_entry_handler(const struct config_entry *entry, void *unused)
+{
+ int ret = 0, i;
+
+ if (!entry || !entry->name || !entry->value) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ /* Check if the option is to be ignored */
+ for (i = 0; i < sizeof(config_ignore_options) / sizeof(char *); i++) {
+ if (!strcmp(entry->name, config_ignore_options[i])) {
+ goto end;
+ }
+ }
+
+ for (i = 0; i < (sizeof(long_options) / sizeof(struct option)) - 1; i++) {
+ /* Ignore if entry name is not fully matched. */
+ if (strcmp(entry->name, long_options[i].name)) {
+ continue;
+ }
+
+ /*
+ * If the option takes no argument on the command line,
+ * we have to check if the value is "true". We support
+ * non-zero numeric values, true, on and yes.
+ */
+ if (!long_options[i].has_arg) {
+ ret = config_parse_value(entry->value);
+ if (ret <= 0) {
+ if (ret) {
+ WARN("Invalid configuration value \"%s\" for option %s",
+ entry->value, entry->name);
+ }
+ /* False, skip boolean config option. */
+ goto end;
+ }
+ }
+
+ ret = set_option(long_options[i].val, entry->value, entry->name);
+ goto end;
+ }
+
+ WARN("Unrecognized option \"%s\" in daemon configuration file.",
+ entry->name);
+
+end:
+ return ret;
+}
+
+static int parse_env_options(void)
+{
+ int ret = 0;
+ char *value = NULL;
+
+ value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_WORKING_DIRECTORY_ENV);
+ if (value) {
+ opt_working_directory = strdup(value);
+ if (!opt_working_directory) {
+ ERR("Failed to allocate working directory string (\"%s\")",
+ value);
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static int set_fd_pool_size(void)
+{
+ int ret = 0;
+ struct rlimit rlimit;
+
+ ret = getrlimit(RLIMIT_NOFILE, &rlimit);
+ if (ret) {
+ PERROR("Failed to get file descriptor limit");
+ ret = -1;
+ goto end;
+ }
+
+ DBG("File descriptor count limits are %" PRIu64 " (soft) and %" PRIu64 " (hard)",
+ (uint64_t) rlimit.rlim_cur,
+ (uint64_t) rlimit.rlim_max);
+ if (lttng_opt_fd_pool_size == -1) {
+ /* Use default value (soft limit - reserve). */
+ if (rlimit.rlim_cur < DEFAULT_RELAYD_MIN_FD_POOL_SIZE) {
+ ERR("The process' file number limit is too low (%" PRIu64 "). The process' file number limit must be set to at least %i.",
+ (uint64_t) rlimit.rlim_cur, DEFAULT_RELAYD_MIN_FD_POOL_SIZE);
+ ret = -1;
+ goto end;
+ }
+ lttng_opt_fd_pool_size = rlimit.rlim_cur -
+ DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE;
+ goto end;
+ }
+
+ if (lttng_opt_fd_pool_size < DEFAULT_RELAYD_MIN_FD_POOL_SIZE) {
+ ERR("File descriptor pool size must be set to at least %d",
+ DEFAULT_RELAYD_MIN_FD_POOL_SIZE);
+ ret = -1;
+ goto end;
+ }
+
+ if (lttng_opt_fd_pool_size > rlimit.rlim_cur) {
+ ERR("File descriptor pool size argument (%u) exceeds the process' soft limit (%" PRIu64 ").",
+ lttng_opt_fd_pool_size, (uint64_t) rlimit.rlim_cur);
+ ret = -1;
+ goto end;
+ }
+
+ DBG("File descriptor pool size argument (%u) adjusted to %u to accommodates transient fd uses",
+ lttng_opt_fd_pool_size,
+ lttng_opt_fd_pool_size - DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE);
+ lttng_opt_fd_pool_size -= DEFAULT_RELAYD_FD_POOL_SIZE_RESERVE;
+end:
+ return ret;
+}
+
+static int set_options(int argc, char **argv)
+{
+ int c, ret = 0, option_index = 0, retval = 0;
+ int orig_optopt = optopt, orig_optind = optind;
+ char *default_address, *optstring;
+ char *config_path = NULL;
+
+ optstring = utils_generate_optstring(long_options,
+ sizeof(long_options) / sizeof(struct option));
+ if (!optstring) {
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ /* Check for the --config option */
+
+ while ((c = getopt_long(argc, argv, optstring, long_options,
+ &option_index)) != -1) {
+ if (c == '?') {
+ retval = -EINVAL;
+ goto exit;
+ } else if (c != 'f') {
+ continue;
+ }
+
+ if (lttng_is_setuid_setgid()) {
+ WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.",
+ "-f, --config");
+ } else {
+ free(config_path);
+ config_path = utils_expand_path(optarg);
+ if (!config_path) {
+ ERR("Failed to resolve path: %s", optarg);
+ }
+ }
+ }
+
+ ret = config_get_section_entries(config_path, config_section_name,
+ config_entry_handler, NULL);
+ if (ret) {
+ if (ret > 0) {
+ ERR("Invalid configuration option at line %i", ret);
+ }
+ retval = -1;
+ goto exit;
+ }
+
+ /* Reset getopt's global state */
+ optopt = orig_optopt;
+ optind = orig_optind;
+ while (1) {
+ c = getopt_long(argc, argv, optstring, long_options, &option_index);
+ if (c == -1) {
+ break;
+ }
+
+ ret = set_option(c, optarg, long_options[option_index].name);
+ if (ret < 0) {
+ retval = -1;
+ goto exit;
+ }
+ }
+
+ /* assign default values */
+ if (control_uri == NULL) {
+ ret = asprintf(&default_address,
+ "tcp://" DEFAULT_NETWORK_CONTROL_BIND_ADDRESS ":%d",
+ DEFAULT_NETWORK_CONTROL_PORT);
+ if (ret < 0) {
+ PERROR("asprintf default data address");
+ retval = -1;
+ goto exit;
+ }
+
+ ret = uri_parse(default_address, &control_uri);
+ free(default_address);
+ if (ret < 0) {
+ ERR("Invalid control URI specified");
+ retval = -1;
+ goto exit;
+ }
+ }
+ if (data_uri == NULL) {
+ ret = asprintf(&default_address,
+ "tcp://" DEFAULT_NETWORK_DATA_BIND_ADDRESS ":%d",
+ DEFAULT_NETWORK_DATA_PORT);
+ if (ret < 0) {
+ PERROR("asprintf default data address");
+ retval = -1;
+ goto exit;
+ }
+
+ ret = uri_parse(default_address, &data_uri);
+ free(default_address);
+ if (ret < 0) {
+ ERR("Invalid data URI specified");
+ retval = -1;
+ goto exit;
+ }
+ }
+ if (live_uri == NULL) {
+ ret = asprintf(&default_address,
+ "tcp://" DEFAULT_NETWORK_VIEWER_BIND_ADDRESS ":%d",
+ DEFAULT_NETWORK_VIEWER_PORT);
+ if (ret < 0) {
+ PERROR("asprintf default viewer control address");
+ retval = -1;
+ goto exit;
+ }
+
+ ret = uri_parse(default_address, &live_uri);
+ free(default_address);
+ if (ret < 0) {
+ ERR("Invalid viewer control URI specified");
+ retval = -1;
+ goto exit;
+ }
+ }
+ ret = set_fd_pool_size();
+ if (ret) {
+ retval = -1;
+ goto exit;
+ }
+
+ if (opt_group_output_by == RELAYD_GROUP_OUTPUT_BY_UNKNOWN) {
+ opt_group_output_by = RELAYD_GROUP_OUTPUT_BY_HOST;
+ }
+ if (opt_allow_clear) {
+ /* Check if env variable exists. */
+ const char *value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_DISALLOW_CLEAR_ENV);
+ if (value) {
+ ret = config_parse_value(value);
+ if (ret < 0) {
+ ERR("Invalid value for %s specified", DEFAULT_LTTNG_RELAYD_DISALLOW_CLEAR_ENV);
+ retval = -1;
+ goto exit;
+ }
+ opt_allow_clear = !ret;
+ }
+ }
+
+exit:
+ free(config_path);
+ free(optstring);
+ return retval;
+}
+
+static void print_global_objects(void)
+{
+ print_viewer_streams();
+ print_relay_streams();
+ print_sessions();
+}
+
+static int noop_close(void *data, int *fds)
+{
+ return 0;
+}
+
+static void untrack_stdio(void)
+{
+ int fds[] = { fileno(stdout), fileno(stderr) };
+
+ /*
+ * noop_close is used since we don't really want to close
+ * the stdio output fds; we merely want to stop tracking them.
+ */
+ (void) fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ fds, 2, noop_close, NULL);
+}
+
+/*
+ * Cleanup the daemon
+ */
+static void relayd_cleanup(void)
+{
+ print_global_objects();
+
+ DBG("Cleaning up");
+
+ if (viewer_streams_ht)
+ lttng_ht_destroy(viewer_streams_ht);
+ if (relay_streams_ht)
+ lttng_ht_destroy(relay_streams_ht);
+ if (sessions_ht)
+ lttng_ht_destroy(sessions_ht);
+
+ free(opt_output_path);
+ free(opt_working_directory);
+
+ if (health_relayd) {
+ health_app_destroy(health_relayd);
+ }
+ /* Close thread quit pipes */
+ if (health_quit_pipe[0] != -1) {
+ (void) fd_tracker_util_pipe_close(
+ the_fd_tracker, health_quit_pipe);
+ }
+ if (thread_quit_pipe[0] != -1) {
+ (void) fd_tracker_util_pipe_close(
+ the_fd_tracker, thread_quit_pipe);
+ }
+ if (sessiond_trace_chunk_registry) {
+ sessiond_trace_chunk_registry_destroy(
+ sessiond_trace_chunk_registry);
+ }
+ if (the_fd_tracker) {
+ untrack_stdio();
+ /*
+ * fd_tracker_destroy() will log the contents of the fd-tracker
+ * if a leak is detected.
+ */
+ fd_tracker_destroy(the_fd_tracker);
+ }
+
+ uri_free(control_uri);
+ uri_free(data_uri);
+ /* Live URI is freed in the live thread. */
+
+ if (tracing_group_name_override) {
+ free((void *) tracing_group_name);
+ }
+}
+
+/*
+ * Write to writable pipe used to notify a thread.
+ */
+static int notify_thread_pipe(int wpipe)
+{
+ ssize_t ret;
+
+ ret = lttng_write(wpipe, "!", 1);
+ if (ret < 1) {
+ PERROR("write poll pipe");
+ goto end;
+ }
+ ret = 0;
+end:
+ return ret;
+}
+
+static int notify_health_quit_pipe(int *pipe)
+{
+ ssize_t ret;
+
+ ret = lttng_write(pipe[1], "4", 1);
+ if (ret < 1) {
+ PERROR("write relay health quit");
+ goto end;
+ }
+ ret = 0;
+end:
+ return ret;
+}
+
+/*
+ * Stop all relayd and relayd-live threads.
+ */
+int lttng_relay_stop_threads(void)
+{
+ int retval = 0;
+
+ /* Stopping all threads */
+ DBG("Terminating all threads");
+ if (notify_thread_pipe(thread_quit_pipe[1])) {
+ ERR("write error on thread quit pipe");
+ retval = -1;
+ }
+
+ if (notify_health_quit_pipe(health_quit_pipe)) {
+ ERR("write error on health quit pipe");
+ }
+
+ /* Dispatch thread */
+ CMM_STORE_SHARED(dispatch_thread_exit, 1);
+ futex_nto1_wake(&relay_conn_queue.futex);
+
+ if (relayd_live_stop()) {
+ ERR("Error stopping live threads");
+ retval = -1;
+ }
+ return retval;
+}
+
+/*
+ * Signal handler for the daemon
+ *
+ * Simply stop all worker threads, leaving main() return gracefully after
+ * joining all threads and calling cleanup().
+ */
+static void sighandler(int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ DBG("SIGINT caught");
+ if (lttng_relay_stop_threads()) {
+ ERR("Error stopping threads");
+ }
+ break;
+ case SIGTERM:
+ DBG("SIGTERM caught");
+ if (lttng_relay_stop_threads()) {
+ ERR("Error stopping threads");
+ }
+ break;
+ case SIGUSR1:
+ CMM_STORE_SHARED(recv_child_signal, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Setup signal handler for :
+ * SIGINT, SIGTERM, SIGPIPE
+ */
+static int set_signal_handler(void)
+{
+ int ret = 0;
+ struct sigaction sa;
+ sigset_t sigset;
+
+ if ((ret = sigemptyset(&sigset)) < 0) {
+ PERROR("sigemptyset");
+ return ret;
+ }
+
+ sa.sa_mask = sigset;
+ sa.sa_flags = 0;
+
+ sa.sa_handler = sighandler;
+ if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) {
+ PERROR("sigaction");
+ return ret;
+ }
+
+ if ((ret = sigaction(SIGINT, &sa, NULL)) < 0) {
+ PERROR("sigaction");
+ return ret;
+ }
+
+ if ((ret = sigaction(SIGUSR1, &sa, NULL)) < 0) {
+ PERROR("sigaction");
+ return ret;
+ }
+
+ sa.sa_handler = SIG_IGN;
+ if ((ret = sigaction(SIGPIPE, &sa, NULL)) < 0) {
+ PERROR("sigaction");
+ return ret;
+ }
+
+ DBG("Signal handler set for SIGTERM, SIGUSR1, SIGPIPE and SIGINT");
+
+ return ret;
+}
+
+void lttng_relay_notify_ready(void)
+{
+ /* Notify the parent of the fork() process that we are ready. */
+ if (opt_daemon || opt_background) {
+ if (uatomic_sub_return(<tng_relay_ready, 1) == 0) {
+ kill(child_ppid, SIGUSR1);
+ }
+ }
+}
+
+/*
+ * Init thread quit pipe.
+ *
+ * Return -1 on error or 0 if all pipes are created.
+ */
+static int init_thread_quit_pipe(void)
+{
+ return fd_tracker_util_pipe_open_cloexec(
+ the_fd_tracker, "Quit pipe", thread_quit_pipe);
+}
+
+/*
+ * Init health quit pipe.
+ *
+ * Return -1 on error or 0 if all pipes are created.
+ */
+static int init_health_quit_pipe(void)
+{
+ return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
+ "Health quit pipe", health_quit_pipe);
+}
+
+/*
+ * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set.
+ */
+static int create_named_thread_poll_set(struct lttng_poll_event *events,
+ int size, const char *name)
+{
+ int ret;
+
+ if (events == NULL || size == 0) {
+ ret = -1;
+ goto error;
+ }
+
+ ret = fd_tracker_util_poll_create(the_fd_tracker,
+ name, events, 1, LTTNG_CLOEXEC);
+ if (ret) {
+ PERROR("Failed to create \"%s\" poll file descriptor", name);
+ goto error;
+ }
+
+ /* Add quit pipe */
+ ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR);
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return ret;
+}
+
+/*
+ * Check if the thread quit pipe was triggered.
+ *
+ * Return 1 if it was triggered else 0;
+ */
+static int check_thread_quit_pipe(int fd, uint32_t events)
+{
+ if (fd == thread_quit_pipe[0] && (events & LPOLLIN)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int create_sock(void *data, int *out_fd)
+{
+ int ret;
+ struct lttcomm_sock *sock = (lttcomm_sock *) data;
+
+ ret = lttcomm_create_sock(sock);
+ if (ret < 0) {
+ goto end;
+ }
+
+ *out_fd = sock->fd;
+end:
+ return ret;
+}
+
+static int close_sock(void *data, int *in_fd)
+{
+ struct lttcomm_sock *sock = (lttcomm_sock *) data;
+
+ return sock->ops->close(sock);
+}
+
+static int accept_sock(void *data, int *out_fd)
+{
+ int ret = 0;
+ /* Socks is an array of in_sock, out_sock. */
+ struct lttcomm_sock **socks = (lttcomm_sock **) data;
+ struct lttcomm_sock *in_sock = socks[0];
+
+ socks[1] = in_sock->ops->accept(in_sock);
+ if (!socks[1]) {
+ ret = -1;
+ goto end;
+ }
+ *out_fd = socks[1]->fd;
+end:
+ return ret;
+}
+
+/*
+ * Create and init socket from uri.
+ */
+static struct lttcomm_sock *relay_socket_create(struct lttng_uri *uri,
+ const char *name)
+{
+ int ret, sock_fd;
+ struct lttcomm_sock *sock = NULL;
+ char uri_str[PATH_MAX];
+ char *formated_name = NULL;
+
+ sock = lttcomm_alloc_sock_from_uri(uri);
+ if (sock == NULL) {
+ ERR("Allocating socket");
+ goto error;
+ }
+
+ /*
+ * Don't fail to create the socket if the name can't be built as it is
+ * only used for debugging purposes.
+ */
+ ret = uri_to_str_url(uri, uri_str, sizeof(uri_str));
+ uri_str[sizeof(uri_str) - 1] = '\0';
+ if (ret >= 0) {
+ ret = asprintf(&formated_name, "%s socket @ %s", name,
+ uri_str);
+ if (ret < 0) {
+ formated_name = NULL;
+ }
+ }
+
+ ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd,
+ (const char **) (formated_name ? &formated_name : NULL),
+ 1, create_sock, sock);
+ if (ret) {
+ PERROR("Failed to open \"%s\" relay socket",
+ formated_name ?: "Unknown");
+ goto error;
+ }
+ DBG("Listening on %s socket %d", name, sock->fd);
+
+ ret = sock->ops->bind(sock);
+ if (ret < 0) {
+ PERROR("Failed to bind socket");
+ goto error;
+ }
+
+ ret = sock->ops->listen(sock, -1);
+ if (ret < 0) {
+ goto error;
+
+ }
+
+ free(formated_name);
+ return sock;
+
+error:
+ if (sock) {
+ lttcomm_destroy_sock(sock);
+ }
+ free(formated_name);
+ return NULL;
+}
+
+static
+struct lttcomm_sock *accept_relayd_sock(struct lttcomm_sock *listening_sock,
+ const char *name)
+{
+ int out_fd, ret;
+ struct lttcomm_sock *socks[2] = { listening_sock, NULL };
+ struct lttcomm_sock *new_sock = NULL;
+
+ ret = fd_tracker_open_unsuspendable_fd(
+ the_fd_tracker, &out_fd,
+ (const char **) &name,
+ 1, accept_sock, &socks);
+ if (ret) {
+ goto end;
+ }
+ new_sock = socks[1];
+ DBG("%s accepted, socket %d", name, new_sock->fd);
+end:
+ return new_sock;
+}
+
+/*
+ * This thread manages the listening for new connections on the network
+ */
+static void *relay_thread_listener(void *data)
+{
+ int i, ret, pollfd, err = -1;
+ uint32_t revents, nb_fd;
+ struct lttng_poll_event events;
+ struct lttcomm_sock *control_sock, *data_sock;
+
+ DBG("[thread] Relay listener started");
+
+ rcu_register_thread();
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_LISTENER);
+
+ health_code_update();
+
+ control_sock = relay_socket_create(control_uri, "Control listener");
+ if (!control_sock) {
+ goto error_sock_control;
+ }
+
+ data_sock = relay_socket_create(data_uri, "Data listener");
+ if (!data_sock) {
+ goto error_sock_relay;
+ }
+
+ /*
+ * Pass 3 as size here for the thread quit pipe, control and
+ * data socket.
+ */
+ ret = create_named_thread_poll_set(&events, 3, "Listener thread epoll");
+ if (ret < 0) {
+ goto error_create_poll;
+ }
+
+ /* Add the control socket */
+ ret = lttng_poll_add(&events, control_sock->fd, LPOLLIN | LPOLLRDHUP);
+ if (ret < 0) {
+ goto error_poll_add;
+ }
+
+ /* Add the data socket */
+ ret = lttng_poll_add(&events, data_sock->fd, LPOLLIN | LPOLLRDHUP);
+ if (ret < 0) {
+ goto error_poll_add;
+ }
+
+ lttng_relay_notify_ready();
+
+ if (testpoint(relayd_thread_listener)) {
+ goto error_testpoint;
+ }
+
+ while (1) {
+ health_code_update();
+
+ DBG("Listener accepting connections");
+
+restart:
+ health_poll_entry();
+ ret = lttng_poll_wait(&events, -1);
+ health_poll_exit();
+ if (ret < 0) {
+ /*
+ * Restart interrupted system call.
+ */
+ if (errno == EINTR) {
+ goto restart;
+ }
+ goto error;
+ }
+
+ nb_fd = ret;
+
+ DBG("Relay new connection received");
+ for (i = 0; i < nb_fd; i++) {
+ health_code_update();
+
+ /* Fetch once the poll data */
+ revents = LTTNG_POLL_GETEV(&events, i);
+ pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ /* Thread quit pipe has been closed. Killing thread. */
+ ret = check_thread_quit_pipe(pollfd, revents);
+ if (ret) {
+ err = 0;
+ goto exit;
+ }
+
+ if (revents & LPOLLIN) {
+ /*
+ * A new connection is requested, therefore a
+ * sessiond/consumerd connection is allocated in
+ * this thread, enqueued to a global queue and
+ * dequeued (and freed) in the worker thread.
+ */
+ int val = 1;
+ struct relay_connection *new_conn;
+ struct lttcomm_sock *newsock = NULL;
+ enum connection_type type;
+
+ if (pollfd == data_sock->fd) {
+ type = RELAY_DATA;
+ newsock = accept_relayd_sock(data_sock,
+ "Data socket to relayd");
+ } else {
+ LTTNG_ASSERT(pollfd == control_sock->fd);
+ type = RELAY_CONTROL;
+ newsock = accept_relayd_sock(control_sock,
+ "Control socket to relayd");
+ }
+ if (!newsock) {
+ PERROR("accepting sock");
+ goto error;
+ }
+
+ ret = setsockopt(newsock->fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ sizeof(val));
+ if (ret < 0) {
+ PERROR("setsockopt inet");
+ lttcomm_destroy_sock(newsock);
+ goto error;
+ }
+
+ ret = socket_apply_keep_alive_config(newsock->fd);
+ if (ret < 0) {
+ ERR("Failed to apply TCP keep-alive configuration on socket (%i)",
+ newsock->fd);
+ lttcomm_destroy_sock(newsock);
+ goto error;
+ }
+
+ new_conn = connection_create(newsock, type);
+ if (!new_conn) {
+ lttcomm_destroy_sock(newsock);
+ goto error;
+ }
+
+ /* Enqueue request for the dispatcher thread. */
+ cds_wfcq_head_ptr_t head;
+ head.h = &relay_conn_queue.head;
+ cds_wfcq_enqueue(head, &relay_conn_queue.tail,
+ &new_conn->qnode);
+
+ /*
+ * Wake the dispatch queue futex.
+ * Implicit memory barrier with the
+ * exchange in cds_wfcq_enqueue.
+ */
+ futex_nto1_wake(&relay_conn_queue.futex);
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ ERR("socket poll error");
+ goto error;
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ goto error;
+ }
+ }
+ }
+
+exit:
+error:
+error_poll_add:
+error_testpoint:
+ (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
+error_create_poll:
+ if (data_sock->fd >= 0) {
+ int data_sock_fd = data_sock->fd;
+
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ &data_sock_fd, 1, close_sock,
+ data_sock);
+ if (ret) {
+ PERROR("Failed to close the data listener socket file descriptor");
+ }
+ data_sock->fd = -1;
+ }
+ lttcomm_destroy_sock(data_sock);
+error_sock_relay:
+ if (control_sock->fd >= 0) {
+ int control_sock_fd = control_sock->fd;
+
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker,
+ &control_sock_fd, 1, close_sock,
+ control_sock);
+ if (ret) {
+ PERROR("Failed to close the control listener socket file descriptor");
+ }
+ control_sock->fd = -1;
+ }
+ lttcomm_destroy_sock(control_sock);
+error_sock_control:
+ if (err) {
+ health_error();
+ ERR("Health error occurred in %s", __func__);
+ }
+ health_unregister(health_relayd);
+ rcu_unregister_thread();
+ DBG("Relay listener thread cleanup complete");
+ lttng_relay_stop_threads();
+ return NULL;
+}
+
+/*
+ * This thread manages the dispatching of the requests to worker threads
+ */
+static void *relay_thread_dispatcher(void *data)
+{
+ int err = -1;
+ ssize_t ret;
+ struct cds_wfcq_node *node;
+ struct relay_connection *new_conn = NULL;
+
+ DBG("[thread] Relay dispatcher started");
+
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_DISPATCHER);
+
+ if (testpoint(relayd_thread_dispatcher)) {
+ goto error_testpoint;
+ }
+
+ health_code_update();
+
+ for (;;) {
+ health_code_update();
+
+ /* Atomically prepare the queue futex */
+ futex_nto1_prepare(&relay_conn_queue.futex);
+
+ if (CMM_LOAD_SHARED(dispatch_thread_exit)) {
+ break;
+ }
+
+ do {
+ health_code_update();
+
+ /* Dequeue commands */
+ node = cds_wfcq_dequeue_blocking(&relay_conn_queue.head,
+ &relay_conn_queue.tail);
+ if (node == NULL) {
+ DBG("Woken up but nothing in the relay command queue");
+ /* Continue thread execution */
+ break;
+ }
+ new_conn = caa_container_of(node, struct relay_connection, qnode);
+
+ DBG("Dispatching request waiting on sock %d", new_conn->sock->fd);
+
+ /*
+ * Inform worker thread of the new request. This
+ * call is blocking so we can be assured that
+ * the data will be read at some point in time
+ * or wait to the end of the world :)
+ */
+ ret = lttng_write(relay_conn_pipe[1], &new_conn, sizeof(new_conn));
+ if (ret < 0) {
+ PERROR("write connection pipe");
+ connection_put(new_conn);
+ goto error;
+ }
+ } while (node != NULL);
+
+ /* Futex wait on queue. Blocking call on futex() */
+ health_poll_entry();
+ futex_nto1_wait(&relay_conn_queue.futex);
+ health_poll_exit();
+ }
+
+ /* Normal exit, no error */
+ err = 0;
+
+error:
+error_testpoint:
+ if (err) {
+ health_error();
+ ERR("Health error occurred in %s", __func__);
+ }
+ health_unregister(health_relayd);
+ DBG("Dispatch thread dying");
+ lttng_relay_stop_threads();
+ return NULL;
+}
+
+static bool session_streams_have_index(const struct relay_session *session)
+{
+ return session->minor >= 4 && !session->snapshot;
+}
+
+/*
+ * Handle the RELAYD_CREATE_SESSION command.
+ *
+ * On success, send back the session id or else return a negative value.
+ */
+static int relay_create_session(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ ssize_t send_ret;
+ struct relay_session *session = NULL;
+ struct lttcomm_relayd_create_session_reply_2_11 reply = {};
+ char session_name[LTTNG_NAME_MAX] = {};
+ char hostname[LTTNG_HOST_NAME_MAX] = {};
+ uint32_t live_timer = 0;
+ bool snapshot = false;
+ bool session_name_contains_creation_timestamp = false;
+ /* Left nil for peers < 2.11. */
+ char base_path[LTTNG_PATH_MAX] = {};
+ lttng_uuid sessiond_uuid = {};
+ LTTNG_OPTIONAL(uint64_t) id_sessiond = {};
+ LTTNG_OPTIONAL(uint64_t) current_chunk_id = {};
+ LTTNG_OPTIONAL(time_t) creation_time = {};
+ struct lttng_dynamic_buffer reply_payload;
+
+ lttng_dynamic_buffer_init(&reply_payload);
+
+ if (conn->minor < 4) {
+ /* From 2.1 to 2.3 */
+ ret = 0;
+ } else if (conn->minor >= 4 && conn->minor < 11) {
+ /* From 2.4 to 2.10 */
+ ret = cmd_create_session_2_4(payload, session_name,
+ hostname, &live_timer, &snapshot);
+ } else {
+ bool has_current_chunk;
+ uint64_t current_chunk_id_value;
+ time_t creation_time_value;
+ uint64_t id_sessiond_value;
+
+ /* From 2.11 to ... */
+ ret = cmd_create_session_2_11(payload, session_name, hostname,
+ base_path, &live_timer, &snapshot, &id_sessiond_value,
+ sessiond_uuid, &has_current_chunk,
+ ¤t_chunk_id_value, &creation_time_value,
+ &session_name_contains_creation_timestamp);
+ if (lttng_uuid_is_nil(sessiond_uuid)) {
+ /* The nil UUID is reserved for pre-2.11 clients. */
+ ERR("Illegal nil UUID announced by peer in create session command");
+ ret = -1;
+ goto send_reply;
+ }
+ LTTNG_OPTIONAL_SET(&id_sessiond, id_sessiond_value);
+ LTTNG_OPTIONAL_SET(&creation_time, creation_time_value);
+ if (has_current_chunk) {
+ LTTNG_OPTIONAL_SET(¤t_chunk_id,
+ current_chunk_id_value);
+ }
+ }
+
+ if (ret < 0) {
+ goto send_reply;
+ }
+
+ session = session_create(session_name, hostname, base_path, live_timer,
+ snapshot, sessiond_uuid,
+ id_sessiond.is_set ? &id_sessiond.value : NULL,
+ current_chunk_id.is_set ? ¤t_chunk_id.value : NULL,
+ creation_time.is_set ? &creation_time.value : NULL,
+ conn->major, conn->minor,
+ session_name_contains_creation_timestamp);
+ if (!session) {
+ ret = -1;
+ goto send_reply;
+ }
+ LTTNG_ASSERT(!conn->session);
+ conn->session = session;
+ DBG("Created session %" PRIu64, session->id);
+
+ reply.generic.session_id = htobe64(session->id);
+
+send_reply:
+ if (ret < 0) {
+ reply.generic.ret_code = htobe32(LTTNG_ERR_FATAL);
+ } else {
+ reply.generic.ret_code = htobe32(LTTNG_OK);
+ }
+
+ if (conn->minor < 11) {
+ /* From 2.1 to 2.10 */
+ ret = lttng_dynamic_buffer_append(&reply_payload,
+ &reply.generic, sizeof(reply.generic));
+ if (ret) {
+ ERR("Failed to append \"create session\" command reply header to payload buffer");
+ ret = -1;
+ goto end;
+ }
+ } else {
+ const uint32_t output_path_length =
+ session ? strlen(session->output_path) + 1 : 0;
+
+ reply.output_path_length = htobe32(output_path_length);
+ ret = lttng_dynamic_buffer_append(
+ &reply_payload, &reply, sizeof(reply));
+ if (ret) {
+ ERR("Failed to append \"create session\" command reply header to payload buffer");
+ goto end;
+ }
+
+ if (output_path_length) {
+ ret = lttng_dynamic_buffer_append(&reply_payload,
+ session->output_path,
+ output_path_length);
+ if (ret) {
+ ERR("Failed to append \"create session\" command reply path to payload buffer");
+ goto end;
+ }
+ }
+ }
+
+ send_ret = conn->sock->ops->sendmsg(conn->sock, reply_payload.data,
+ reply_payload.size, 0);
+ if (send_ret < (ssize_t) reply_payload.size) {
+ ERR("Failed to send \"create session\" command reply of %zu bytes (ret = %zd)",
+ reply_payload.size, send_ret);
+ ret = -1;
+ }
+end:
+ if (ret < 0 && session) {
+ session_put(session);
+ }
+ lttng_dynamic_buffer_reset(&reply_payload);
+ return ret;
+}
+
+/*
+ * When we have received all the streams and the metadata for a channel,
+ * we make them visible to the viewer threads.
+ */
+static void publish_connection_local_streams(struct relay_connection *conn)
+{
+ struct relay_stream *stream;
+ struct relay_session *session = conn->session;
+
+ /*
+ * We publish all streams belonging to a session atomically wrt
+ * session lock.
+ */
+ pthread_mutex_lock(&session->lock);
+ rcu_read_lock();
+ cds_list_for_each_entry_rcu(stream, &session->recv_list,
+ recv_node) {
+ stream_publish(stream);
+ }
+ rcu_read_unlock();
+
+ /*
+ * Inform the viewer that there are new streams in the session.
+ */
+ if (session->viewer_attached) {
+ uatomic_set(&session->new_streams, 1);
+ }
+ pthread_mutex_unlock(&session->lock);
+}
+
+static int conform_channel_path(char *channel_path)
+{
+ int ret = 0;
+
+ if (strstr("../", channel_path)) {
+ ERR("Refusing channel path as it walks up the path hierarchy: \"%s\"",
+ channel_path);
+ ret = -1;
+ goto end;
+ }
+
+ if (*channel_path == '/') {
+ const size_t len = strlen(channel_path);
+
+ /*
+ * Channel paths from peers prior to 2.11 are expressed as an
+ * absolute path that is, in reality, relative to the relay
+ * daemon's output directory. Remove the leading slash so it
+ * is correctly interpreted as a relative path later on.
+ *
+ * len (and not len - 1) is used to copy the trailing NULL.
+ */
+ bcopy(channel_path + 1, channel_path, len);
+ }
+end:
+ return ret;
+}
+
+/*
+ * relay_add_stream: allocate a new stream for a session
+ */
+static int relay_add_stream(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct relay_stream *stream = NULL;
+ struct lttcomm_relayd_status_stream reply;
+ struct ctf_trace *trace = NULL;
+ uint64_t stream_handle = -1ULL;
+ char *path_name = NULL, *channel_name = NULL;
+ uint64_t tracefile_size = 0, tracefile_count = 0;
+ LTTNG_OPTIONAL(uint64_t) stream_chunk_id = {};
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to add a stream before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (session->minor == 1) {
+ /* For 2.1 */
+ ret = cmd_recv_stream_2_1(payload, &path_name,
+ &channel_name);
+ } else if (session->minor > 1 && session->minor < 11) {
+ /* From 2.2 to 2.10 */
+ ret = cmd_recv_stream_2_2(payload, &path_name,
+ &channel_name, &tracefile_size, &tracefile_count);
+ } else {
+ /* From 2.11 to ... */
+ ret = cmd_recv_stream_2_11(payload, &path_name,
+ &channel_name, &tracefile_size, &tracefile_count,
+ &stream_chunk_id.value);
+ stream_chunk_id.is_set = true;
+ }
+
+ if (ret < 0) {
+ goto send_reply;
+ }
+
+ if (conform_channel_path(path_name)) {
+ goto send_reply;
+ }
+
+ /*
+ * Backward compatibility for --group-output-by-session.
+ * Prior to lttng 2.11, the complete path is passed by the stream.
+ * Starting at 2.11, lttng-relayd uses chunk. When dealing with producer
+ * >=2.11 the chunk is responsible for the output path. When dealing
+ * with producer < 2.11 the chunk output_path is the root output path
+ * and the stream carries the complete path (path_name).
+ * To support --group-output-by-session with older producer (<2.11), we
+ * need to craft the path based on the stream path.
+ */
+ if (opt_group_output_by == RELAYD_GROUP_OUTPUT_BY_SESSION) {
+ if (conn->minor < 4) {
+ /*
+ * From 2.1 to 2.3, the session_name is not passed on
+ * the RELAYD_CREATE_SESSION command. The session name
+ * is necessary to detect the presence of a base_path
+ * inside the stream path. Without it we cannot perform
+ * a valid group-output-by-session transformation.
+ */
+ WARN("Unable to perform a --group-by-session transformation for session %" PRIu64
+ " for stream with path \"%s\" as it is produced by a peer using a protocol older than v2.4",
+ session->id, path_name);
+ } else if (conn->minor >= 4 && conn->minor < 11) {
+ char *group_by_session_path_name;
+
+ LTTNG_ASSERT(session->session_name[0] != '\0');
+
+ group_by_session_path_name =
+ backward_compat_group_by_session(
+ path_name,
+ session->session_name,
+ session->creation_time.value);
+ if (!group_by_session_path_name) {
+ ERR("Failed to apply group by session to stream of session %" PRIu64,
+ session->id);
+ goto send_reply;
+ }
+
+ DBG("Transformed session path from \"%s\" to \"%s\" to honor per-session name grouping",
+ path_name, group_by_session_path_name);
+
+ free(path_name);
+ path_name = group_by_session_path_name;
+ }
+ }
+
+ trace = ctf_trace_get_by_path_or_create(session, path_name);
+ if (!trace) {
+ goto send_reply;
+ }
+
+ /* This stream here has one reference on the trace. */
+ pthread_mutex_lock(&last_relay_stream_id_lock);
+ stream_handle = ++last_relay_stream_id;
+ pthread_mutex_unlock(&last_relay_stream_id_lock);
+
+ /* We pass ownership of path_name and channel_name. */
+ stream = stream_create(trace, stream_handle, path_name,
+ channel_name, tracefile_size, tracefile_count);
+ path_name = NULL;
+ channel_name = NULL;
+
+ /*
+ * Streams are the owners of their trace. Reference to trace is
+ * kept within stream_create().
+ */
+ ctf_trace_put(trace);
+
+send_reply:
+ memset(&reply, 0, sizeof(reply));
+ reply.handle = htobe64(stream_handle);
+ if (!stream) {
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ } else {
+ reply.ret_code = htobe32(LTTNG_OK);
+ }
+
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(struct lttcomm_relayd_status_stream), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"add stream\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+
+end_no_session:
+ free(path_name);
+ free(channel_name);
+ return ret;
+}
+
+/*
+ * relay_close_stream: close a specific stream
+ */
+static int relay_close_stream(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_close_stream stream_info;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+
+ DBG("Close stream received");
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to close a stream before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(stream_info)) {
+ ERR("Unexpected payload size in \"relay_close_stream\": expected >= %zu bytes, got %zu bytes",
+ sizeof(stream_info), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&stream_info, payload->data, sizeof(stream_info));
+ stream_info.stream_id = be64toh(stream_info.stream_id);
+ stream_info.last_net_seq_num = be64toh(stream_info.last_net_seq_num);
+
+ stream = stream_get_by_id(stream_info.stream_id);
+ if (!stream) {
+ ret = -1;
+ goto end;
+ }
+
+ /*
+ * Set last_net_seq_num before the close flag. Required by data
+ * pending check.
+ */
+ pthread_mutex_lock(&stream->lock);
+ stream->last_net_seq_num = stream_info.last_net_seq_num;
+ pthread_mutex_unlock(&stream->lock);
+
+ /*
+ * This is one of the conditions which may trigger a stream close
+ * with the others being:
+ * 1) A close command is received for a stream
+ * 2) The control connection owning the stream is closed
+ * 3) We have received all of the stream's data _after_ a close
+ * request.
+ */
+ try_stream_close(stream);
+ if (stream->is_metadata) {
+ struct relay_viewer_stream *vstream;
+
+ vstream = viewer_stream_get_by_id(stream->stream_handle);
+ if (vstream) {
+ if (stream->no_new_metadata_notified) {
+ /*
+ * Since all the metadata has been sent to the
+ * viewer and that we have a request to close
+ * its stream, we can safely teardown the
+ * corresponding metadata viewer stream.
+ */
+ viewer_stream_put(vstream);
+ }
+ /* Put local reference. */
+ viewer_stream_put(vstream);
+ }
+ }
+ stream_put(stream);
+ ret = 0;
+
+end:
+ memset(&reply, 0, sizeof(reply));
+ if (ret < 0) {
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ } else {
+ reply.ret_code = htobe32(LTTNG_OK);
+ }
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(struct lttcomm_relayd_generic_reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"close stream\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * relay_reset_metadata: reset a metadata stream
+ */
+static
+int relay_reset_metadata(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_reset_metadata stream_info;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+
+ DBG("Reset metadata received");
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to reset a metadata stream before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(stream_info)) {
+ ERR("Unexpected payload size in \"relay_reset_metadata\": expected >= %zu bytes, got %zu bytes",
+ sizeof(stream_info), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&stream_info, payload->data, sizeof(stream_info));
+ stream_info.stream_id = be64toh(stream_info.stream_id);
+ stream_info.version = be64toh(stream_info.version);
+
+ DBG("Update metadata to version %" PRIu64, stream_info.version);
+
+ /* Unsupported for live sessions for now. */
+ if (session->live_timer != 0) {
+ ret = -1;
+ goto end;
+ }
+
+ stream = stream_get_by_id(stream_info.stream_id);
+ if (!stream) {
+ ret = -1;
+ goto end;
+ }
+ pthread_mutex_lock(&stream->lock);
+ if (!stream->is_metadata) {
+ ret = -1;
+ goto end_unlock;
+ }
+
+ ret = stream_reset_file(stream);
+ if (ret < 0) {
+ ERR("Failed to reset metadata stream %" PRIu64
+ ": stream_path = %s, channel = %s",
+ stream->stream_handle, stream->path_name,
+ stream->channel_name);
+ goto end_unlock;
+ }
+end_unlock:
+ pthread_mutex_unlock(&stream->lock);
+ stream_put(stream);
+
+end:
+ memset(&reply, 0, sizeof(reply));
+ if (ret < 0) {
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ } else {
+ reply.ret_code = htobe32(LTTNG_OK);
+ }
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(struct lttcomm_relayd_generic_reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"reset metadata\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * relay_unknown_command: send -1 if received unknown command
+ */
+static void relay_unknown_command(struct relay_connection *conn)
+{
+ struct lttcomm_relayd_generic_reply reply;
+ ssize_t send_ret;
+
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < sizeof(reply)) {
+ ERR("Failed to send \"unknown command\" command reply (ret = %zd)", send_ret);
+ }
+}
+
+/*
+ * relay_start: send an acknowledgment to the client to tell if we are
+ * ready to receive data. We are ready if a session is established.
+ */
+static int relay_start(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ ssize_t send_ret;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_session *session = conn->session;
+
+ if (!session) {
+ DBG("Trying to start the streaming without a session established");
+ ret = htobe32(LTTNG_ERR_UNK);
+ }
+
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(LTTNG_OK);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"relay_start\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/*
+ * relay_recv_metadata: receive the metadata for the session.
+ */
+static int relay_recv_metadata(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_metadata_payload metadata_payload_header;
+ struct relay_stream *metadata_stream;
+ uint64_t metadata_payload_size;
+ struct lttng_buffer_view packet_view;
+
+ if (!session) {
+ ERR("Metadata sent before version check");
+ ret = -1;
+ goto end;
+ }
+
+ if (recv_hdr->data_size < sizeof(struct lttcomm_relayd_metadata_payload)) {
+ ERR("Incorrect data size");
+ ret = -1;
+ goto end;
+ }
+ metadata_payload_size = recv_hdr->data_size -
+ sizeof(struct lttcomm_relayd_metadata_payload);
+
+ memcpy(&metadata_payload_header, payload->data,
+ sizeof(metadata_payload_header));
+ metadata_payload_header.stream_id = be64toh(
+ metadata_payload_header.stream_id);
+ metadata_payload_header.padding_size = be32toh(
+ metadata_payload_header.padding_size);
+
+ metadata_stream = stream_get_by_id(metadata_payload_header.stream_id);
+ if (!metadata_stream) {
+ ret = -1;
+ goto end;
+ }
+
+ packet_view = lttng_buffer_view_from_view(payload,
+ sizeof(metadata_payload_header), metadata_payload_size);
+ if (!lttng_buffer_view_is_valid(&packet_view)) {
+ ERR("Invalid metadata packet length announced by header");
+ ret = -1;
+ goto end_put;
+ }
+
+ pthread_mutex_lock(&metadata_stream->lock);
+ ret = stream_write(metadata_stream, &packet_view,
+ metadata_payload_header.padding_size);
+ pthread_mutex_unlock(&metadata_stream->lock);
+ if (ret){
+ ret = -1;
+ goto end_put;
+ }
+end_put:
+ stream_put(metadata_stream);
+end:
+ return ret;
+}
+
+/*
+ * relay_send_version: send relayd version number
+ */
+static int relay_send_version(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct lttcomm_relayd_version reply, msg;
+ bool compatible = true;
+
+ conn->version_check_done = true;
+
+ /* Get version from the other side. */
+ if (payload->size < sizeof(msg)) {
+ ERR("Unexpected payload size in \"relay_send_version\": expected >= %zu bytes, got %zu bytes",
+ sizeof(msg), payload->size);
+ ret = -1;
+ goto end;
+ }
+
+ memcpy(&msg, payload->data, sizeof(msg));
+ msg.major = be32toh(msg.major);
+ msg.minor = be32toh(msg.minor);
+
+ memset(&reply, 0, sizeof(reply));
+ reply.major = RELAYD_VERSION_COMM_MAJOR;
+ reply.minor = RELAYD_VERSION_COMM_MINOR;
+
+ /* Major versions must be the same */
+ if (reply.major != msg.major) {
+ DBG("Incompatible major versions (%u vs %u), deleting session",
+ reply.major, msg.major);
+ compatible = false;
+ }
+
+ conn->major = reply.major;
+ /* We adapt to the lowest compatible version */
+ if (reply.minor <= msg.minor) {
+ conn->minor = reply.minor;
+ } else {
+ conn->minor = msg.minor;
+ }
+
+ reply.major = htobe32(reply.major);
+ reply.minor = htobe32(reply.minor);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"send version\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ goto end;
+ } else {
+ ret = 0;
+ }
+
+ if (!compatible) {
+ ret = -1;
+ goto end;
+ }
+
+ DBG("Version check done using protocol %u.%u", conn->major,
+ conn->minor);
+
+end:
+ return ret;
+}
+
+/*
+ * Check for data pending for a given stream id from the session daemon.
+ */
+static int relay_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_data_pending msg;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+ ssize_t send_ret;
+ int ret;
+ uint64_t stream_seq;
+
+ DBG("Data pending command received");
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to check for data before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(msg)) {
+ ERR("Unexpected payload size in \"relay_data_pending\": expected >= %zu bytes, got %zu bytes",
+ sizeof(msg), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&msg, payload->data, sizeof(msg));
+ msg.stream_id = be64toh(msg.stream_id);
+ msg.last_net_seq_num = be64toh(msg.last_net_seq_num);
+
+ stream = stream_get_by_id(msg.stream_id);
+ if (stream == NULL) {
+ ret = -1;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+
+ if (session_streams_have_index(session)) {
+ /*
+ * Ensure that both the index and stream data have been
+ * flushed up to the requested point.
+ */
+ stream_seq = std::min(stream->prev_data_seq, stream->prev_index_seq);
+ } else {
+ stream_seq = stream->prev_data_seq;
+ }
+ DBG("Data pending for stream id %" PRIu64 ": prev_data_seq %" PRIu64
+ ", prev_index_seq %" PRIu64
+ ", and last_seq %" PRIu64, msg.stream_id,
+ stream->prev_data_seq, stream->prev_index_seq,
+ msg.last_net_seq_num);
+
+ /* Avoid wrapping issue */
+ if (((int64_t) (stream_seq - msg.last_net_seq_num)) >= 0) {
+ /* Data has in fact been written and is NOT pending */
+ ret = 0;
+ } else {
+ /* Data still being streamed thus pending */
+ ret = 1;
+ }
+
+ stream->data_pending_check_done = true;
+ pthread_mutex_unlock(&stream->lock);
+
+ stream_put(stream);
+end:
+
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(ret);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"data pending\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * Wait for the control socket to reach a quiescent state.
+ *
+ * Note that for now, when receiving this command from the session
+ * daemon, this means that every subsequent commands or data received on
+ * the control socket has been handled. So, this is why we simply return
+ * OK here.
+ */
+static int relay_quiescent_control(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct relay_stream *stream;
+ struct lttcomm_relayd_quiescent_control msg;
+ struct lttcomm_relayd_generic_reply reply;
+
+ DBG("Checking quiescent state on control socket");
+
+ if (!conn->session || !conn->version_check_done) {
+ ERR("Trying to check for data before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(msg)) {
+ ERR("Unexpected payload size in \"relay_quiescent_control\": expected >= %zu bytes, got %zu bytes",
+ sizeof(msg), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&msg, payload->data, sizeof(msg));
+ msg.stream_id = be64toh(msg.stream_id);
+
+ stream = stream_get_by_id(msg.stream_id);
+ if (!stream) {
+ goto reply;
+ }
+ pthread_mutex_lock(&stream->lock);
+ stream->data_pending_check_done = true;
+ pthread_mutex_unlock(&stream->lock);
+
+ DBG("Relay quiescent control pending flag set to %" PRIu64, msg.stream_id);
+ stream_put(stream);
+reply:
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(LTTNG_OK);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"quiescent control\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * Initialize a data pending command. This means that a consumer is about
+ * to ask for data pending for each stream it holds. Simply iterate over
+ * all streams of a session and set the data_pending_check_done flag.
+ *
+ * This command returns to the client a LTTNG_OK code.
+ */
+static int relay_begin_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct lttng_ht_iter iter;
+ struct lttcomm_relayd_begin_data_pending msg;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+
+ LTTNG_ASSERT(recv_hdr);
+ LTTNG_ASSERT(conn);
+
+ DBG("Init streams for data pending");
+
+ if (!conn->session || !conn->version_check_done) {
+ ERR("Trying to check for data before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(msg)) {
+ ERR("Unexpected payload size in \"relay_begin_data_pending\": expected >= %zu bytes, got %zu bytes",
+ sizeof(msg), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&msg, payload->data, sizeof(msg));
+ msg.session_id = be64toh(msg.session_id);
+
+ /*
+ * Iterate over all streams to set the begin data pending flag.
+ * For now, the streams are indexed by stream handle so we have
+ * to iterate over all streams to find the one associated with
+ * the right session_id.
+ */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
+ node.node) {
+ if (!stream_get(stream)) {
+ continue;
+ }
+ if (stream->trace->session->id == msg.session_id) {
+ pthread_mutex_lock(&stream->lock);
+ stream->data_pending_check_done = false;
+ pthread_mutex_unlock(&stream->lock);
+ DBG("Set begin data pending flag to stream %" PRIu64,
+ stream->stream_handle);
+ }
+ stream_put(stream);
+ }
+ rcu_read_unlock();
+
+ memset(&reply, 0, sizeof(reply));
+ /* All good, send back reply. */
+ reply.ret_code = htobe32(LTTNG_OK);
+
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"begin data pending\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * End data pending command. This will check, for a given session id, if
+ * each stream associated with it has its data_pending_check_done flag
+ * set. If not, this means that the client lost track of the stream but
+ * the data is still being streamed on our side. In this case, we inform
+ * the client that data is in flight.
+ *
+ * Return to the client if there is data in flight or not with a ret_code.
+ */
+static int relay_end_data_pending(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct lttng_ht_iter iter;
+ struct lttcomm_relayd_end_data_pending msg;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+ uint32_t is_data_inflight = 0;
+
+ DBG("End data pending command");
+
+ if (!conn->session || !conn->version_check_done) {
+ ERR("Trying to check for data before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ if (payload->size < sizeof(msg)) {
+ ERR("Unexpected payload size in \"relay_end_data_pending\": expected >= %zu bytes, got %zu bytes",
+ sizeof(msg), payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&msg, payload->data, sizeof(msg));
+ msg.session_id = be64toh(msg.session_id);
+
+ /*
+ * Iterate over all streams to see if the begin data pending
+ * flag is set.
+ */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
+ node.node) {
+ if (!stream_get(stream)) {
+ continue;
+ }
+ if (stream->trace->session->id != msg.session_id) {
+ stream_put(stream);
+ continue;
+ }
+ pthread_mutex_lock(&stream->lock);
+ if (!stream->data_pending_check_done) {
+ uint64_t stream_seq;
+
+ if (session_streams_have_index(conn->session)) {
+ /*
+ * Ensure that both the index and stream data have been
+ * flushed up to the requested point.
+ */
+ stream_seq = std::min(stream->prev_data_seq, stream->prev_index_seq);
+ } else {
+ stream_seq = stream->prev_data_seq;
+ }
+ if (!stream->closed || !(((int64_t) (stream_seq - stream->last_net_seq_num)) >= 0)) {
+ is_data_inflight = 1;
+ DBG("Data is still in flight for stream %" PRIu64,
+ stream->stream_handle);
+ pthread_mutex_unlock(&stream->lock);
+ stream_put(stream);
+ break;
+ }
+ }
+ pthread_mutex_unlock(&stream->lock);
+ stream_put(stream);
+ }
+ rcu_read_unlock();
+
+ memset(&reply, 0, sizeof(reply));
+ /* All good, send back reply. */
+ reply.ret_code = htobe32(is_data_inflight);
+
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"end data pending\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * Receive an index for a specific stream.
+ *
+ * Return 0 on success else a negative value.
+ */
+static int relay_recv_index(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_index index_info;
+ struct lttcomm_relayd_generic_reply reply;
+ struct relay_stream *stream;
+ size_t msg_len;
+
+ LTTNG_ASSERT(conn);
+
+ DBG("Relay receiving index");
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to close a stream before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ msg_len = lttcomm_relayd_index_len(
+ lttng_to_index_major(conn->major, conn->minor),
+ lttng_to_index_minor(conn->major, conn->minor));
+ if (payload->size < msg_len) {
+ ERR("Unexpected payload size in \"relay_recv_index\": expected >= %zu bytes, got %zu bytes",
+ msg_len, payload->size);
+ ret = -1;
+ goto end_no_session;
+ }
+ memcpy(&index_info, payload->data, msg_len);
+ index_info.relay_stream_id = be64toh(index_info.relay_stream_id);
+ index_info.net_seq_num = be64toh(index_info.net_seq_num);
+ index_info.packet_size = be64toh(index_info.packet_size);
+ index_info.content_size = be64toh(index_info.content_size);
+ index_info.timestamp_begin = be64toh(index_info.timestamp_begin);
+ index_info.timestamp_end = be64toh(index_info.timestamp_end);
+ index_info.events_discarded = be64toh(index_info.events_discarded);
+ index_info.stream_id = be64toh(index_info.stream_id);
+
+ if (conn->minor >= 8) {
+ index_info.stream_instance_id =
+ be64toh(index_info.stream_instance_id);
+ index_info.packet_seq_num = be64toh(index_info.packet_seq_num);
+ } else {
+ index_info.stream_instance_id = -1ULL;
+ index_info.packet_seq_num = -1ULL;
+ }
+
+ stream = stream_get_by_id(index_info.relay_stream_id);
+ if (!stream) {
+ ERR("stream_get_by_id not found");
+ ret = -1;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+ ret = stream_add_index(stream, &index_info);
+ pthread_mutex_unlock(&stream->lock);
+ if (ret) {
+ goto end_stream_put;
+ }
+
+end_stream_put:
+ stream_put(stream);
+end:
+ memset(&reply, 0, sizeof(reply));
+ if (ret < 0) {
+ reply.ret_code = htobe32(LTTNG_ERR_UNK);
+ } else {
+ reply.ret_code = htobe32(LTTNG_OK);
+ }
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"recv index\" command reply (ret = %zd)", send_ret);
+ ret = -1;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * Receive the streams_sent message.
+ *
+ * Return 0 on success else a negative value.
+ */
+static int relay_streams_sent(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret;
+ ssize_t send_ret;
+ struct lttcomm_relayd_generic_reply reply;
+
+ LTTNG_ASSERT(conn);
+
+ DBG("Relay receiving streams_sent");
+
+ if (!conn->session || !conn->version_check_done) {
+ ERR("Trying to close a stream before version check");
+ ret = -1;
+ goto end_no_session;
+ }
+
+ /*
+ * Publish every pending stream in the connection recv list which are
+ * now ready to be used by the viewer.
+ */
+ publish_connection_local_streams(conn);
+
+ memset(&reply, 0, sizeof(reply));
+ reply.ret_code = htobe32(LTTNG_OK);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"streams sent\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ } else {
+ /* Success. */
+ ret = 0;
+ }
+
+end_no_session:
+ return ret;
+}
+
+/*
+ * relay_rotate_session_stream: rotate a stream to a new tracefile for the
+ * session rotation feature (not the tracefile rotation feature).
+ */
+static int relay_rotate_session_streams(
+ const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ uint32_t i;
+ ssize_t send_ret;
+ enum lttng_error_code reply_code = LTTNG_ERR_UNK;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_rotate_streams rotate_streams;
+ struct lttcomm_relayd_generic_reply reply = {};
+ struct relay_stream *stream = NULL;
+ const size_t header_len = sizeof(struct lttcomm_relayd_rotate_streams);
+ struct lttng_trace_chunk *next_trace_chunk = NULL;
+ struct lttng_buffer_view stream_positions;
+ char chunk_id_buf[MAX_INT_DEC_LEN(uint64_t)];
+ const char *chunk_id_str = "none";
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to rotate a stream before version check");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ if (session->major == 2 && session->minor < 11) {
+ ERR("Unsupported feature before 2.11");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ if (payload->size < header_len) {
+ ERR("Unexpected payload size in \"relay_rotate_session_stream\": expected >= %zu bytes, got %zu bytes",
+ header_len, payload->size);
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ memcpy(&rotate_streams, payload->data, header_len);
+
+ /* Convert header to host endianness. */
+ rotate_streams = (typeof(rotate_streams)) {
+ .stream_count = be32toh(rotate_streams.stream_count),
+ .new_chunk_id = (typeof(rotate_streams.new_chunk_id)) {
+ .is_set = !!rotate_streams.new_chunk_id.is_set,
+ .value = be64toh(rotate_streams.new_chunk_id.value),
+ }
+ };
+
+ if (rotate_streams.new_chunk_id.is_set) {
+ /*
+ * Retrieve the trace chunk the stream must transition to. As
+ * per the protocol, this chunk should have been created
+ * before this command is received.
+ */
+ next_trace_chunk = sessiond_trace_chunk_registry_get_chunk(
+ sessiond_trace_chunk_registry,
+ session->sessiond_uuid, session->id,
+ rotate_streams.new_chunk_id.value);
+ if (!next_trace_chunk) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(session->sessiond_uuid, uuid_str);
+ ERR("Unknown next trace chunk in ROTATE_STREAMS command: sessiond_uuid = {%s}, session_id = %" PRIu64
+ ", trace_chunk_id = %" PRIu64,
+ uuid_str, session->id,
+ rotate_streams.new_chunk_id.value);
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ ret = -1;
+ goto end;
+ }
+
+ ret = snprintf(chunk_id_buf, sizeof(chunk_id_buf), "%" PRIu64,
+ rotate_streams.new_chunk_id.value);
+ if (ret < 0 || ret >= sizeof(chunk_id_buf)) {
+ chunk_id_str = "formatting error";
+ } else {
+ chunk_id_str = chunk_id_buf;
+ }
+ }
+
+ DBG("Rotate %" PRIu32 " streams of session \"%s\" to chunk \"%s\"",
+ rotate_streams.stream_count, session->session_name,
+ chunk_id_str);
+
+ stream_positions = lttng_buffer_view_from_view(payload,
+ sizeof(rotate_streams), -1);
+ if (!stream_positions.data ||
+ stream_positions.size <
+ (rotate_streams.stream_count *
+ sizeof(struct lttcomm_relayd_stream_rotation_position))) {
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ ret = -1;
+ goto end;
+ }
+
+ for (i = 0; i < rotate_streams.stream_count; i++) {
+ struct lttcomm_relayd_stream_rotation_position *position_comm =
+ &((typeof(position_comm)) stream_positions.data)[i];
+ const struct lttcomm_relayd_stream_rotation_position pos = {
+ .stream_id = be64toh(position_comm->stream_id),
+ .rotate_at_seq_num = be64toh(
+ position_comm->rotate_at_seq_num),
+ };
+
+ stream = stream_get_by_id(pos.stream_id);
+ if (!stream) {
+ reply_code = LTTNG_ERR_INVALID;
+ ret = -1;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+ ret = stream_set_pending_rotation(stream, next_trace_chunk,
+ pos.rotate_at_seq_num);
+ pthread_mutex_unlock(&stream->lock);
+ if (ret) {
+ reply_code = LTTNG_ERR_FILE_CREATION_ERROR;
+ goto end;
+ }
+
+ stream_put(stream);
+ stream = NULL;
+ }
+
+ reply_code = LTTNG_OK;
+ ret = 0;
+end:
+ if (stream) {
+ stream_put(stream);
+ }
+
+ reply.ret_code = htobe32((uint32_t) reply_code);
+ send_ret = conn->sock->ops->sendmsg(conn->sock, &reply,
+ sizeof(struct lttcomm_relayd_generic_reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"rotate session stream\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+end_no_reply:
+ lttng_trace_chunk_put(next_trace_chunk);
+ return ret;
+}
+
+
+
+/*
+ * relay_create_trace_chunk: create a new trace chunk
+ */
+static int relay_create_trace_chunk(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_create_trace_chunk *msg;
+ struct lttcomm_relayd_generic_reply reply = {};
+ struct lttng_buffer_view header_view;
+ struct lttng_trace_chunk *chunk = NULL, *published_chunk = NULL;
+ enum lttng_error_code reply_code = LTTNG_OK;
+ enum lttng_trace_chunk_status chunk_status;
+ const char *new_path;
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to create a trace chunk before version check");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ if (session->major == 2 && session->minor < 11) {
+ ERR("Chunk creation command is unsupported before 2.11");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
+ if (!lttng_buffer_view_is_valid(&header_view)) {
+ ERR("Failed to receive payload of chunk creation command");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ /* Convert to host endianness. */
+ msg = (typeof(msg)) header_view.data;
+ msg->chunk_id = be64toh(msg->chunk_id);
+ msg->creation_timestamp = be64toh(msg->creation_timestamp);
+ msg->override_name_length = be32toh(msg->override_name_length);
+
+ if (session->current_trace_chunk &&
+ !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) {
+ chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk,
+ DEFAULT_CHUNK_TMP_OLD_DIRECTORY);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ERR("Failed to rename old chunk");
+ ret = -1;
+ reply_code = LTTNG_ERR_UNK;
+ goto end;
+ }
+ }
+ session->ongoing_rotation = true;
+ if (!session->current_trace_chunk) {
+ if (!session->has_rotated) {
+ new_path = "";
+ } else {
+ new_path = NULL;
+ }
+ } else {
+ new_path = DEFAULT_CHUNK_TMP_NEW_DIRECTORY;
+ }
+ chunk = lttng_trace_chunk_create(
+ msg->chunk_id, msg->creation_timestamp, new_path);
+ if (!chunk) {
+ ERR("Failed to create trace chunk in trace chunk creation command");
+ ret = -1;
+ reply_code = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+ lttng_trace_chunk_set_fd_tracker(chunk, the_fd_tracker);
+
+ if (msg->override_name_length) {
+ const char *name;
+ const struct lttng_buffer_view chunk_name_view =
+ lttng_buffer_view_from_view(payload,
+ sizeof(*msg),
+ msg->override_name_length);
+
+ if (!lttng_buffer_view_is_valid(&chunk_name_view)) {
+ ERR("Invalid payload of chunk creation command (protocol error): buffer too short for expected name length");
+ ret = -1;
+ reply_code = LTTNG_ERR_INVALID;
+ goto end;
+ }
+
+ name = chunk_name_view.data;
+ if (name[msg->override_name_length - 1]) {
+ ERR("Invalid payload of chunk creation command (protocol error): name is not null-terminated");
+ ret = -1;
+ reply_code = LTTNG_ERR_INVALID;
+ goto end;
+ }
+
+ chunk_status = lttng_trace_chunk_override_name(
+ chunk, chunk_name_view.data);
+ switch (chunk_status) {
+ case LTTNG_TRACE_CHUNK_STATUS_OK:
+ break;
+ case LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT:
+ ERR("Failed to set the name of new trace chunk in trace chunk creation command (invalid name)");
+ reply_code = LTTNG_ERR_INVALID;
+ ret = -1;
+ goto end;
+ default:
+ ERR("Failed to set the name of new trace chunk in trace chunk creation command (unknown error)");
+ reply_code = LTTNG_ERR_UNK;
+ ret = -1;
+ goto end;
+ }
+ }
+
+ chunk_status = lttng_trace_chunk_set_credentials_current_user(chunk);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ reply_code = LTTNG_ERR_UNK;
+ ret = -1;
+ goto end;
+ }
+
+ LTTNG_ASSERT(conn->session->output_directory);
+ chunk_status = lttng_trace_chunk_set_as_owner(chunk,
+ conn->session->output_directory);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ reply_code = LTTNG_ERR_UNK;
+ ret = -1;
+ goto end;
+ }
+
+ published_chunk = sessiond_trace_chunk_registry_publish_chunk(
+ sessiond_trace_chunk_registry,
+ conn->session->sessiond_uuid,
+ conn->session->id,
+ chunk);
+ if (!published_chunk) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(conn->session->sessiond_uuid, uuid_str);
+ ERR("Failed to publish chunk: sessiond_uuid = %s, session_id = %" PRIu64 ", chunk_id = %" PRIu64,
+ uuid_str,
+ conn->session->id,
+ msg->chunk_id);
+ ret = -1;
+ reply_code = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+
+ pthread_mutex_lock(&conn->session->lock);
+ if (conn->session->pending_closure_trace_chunk) {
+ /*
+ * Invalid; this means a second create_trace_chunk command was
+ * received before a close_trace_chunk.
+ */
+ ERR("Invalid trace chunk close command received; a trace chunk is already waiting for a trace chunk close command");
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ ret = -1;
+ goto end_unlock_session;
+ }
+ conn->session->pending_closure_trace_chunk =
+ conn->session->current_trace_chunk;
+ conn->session->current_trace_chunk = published_chunk;
+ published_chunk = NULL;
+ if (!conn->session->pending_closure_trace_chunk) {
+ session->ongoing_rotation = false;
+ }
+end_unlock_session:
+ pthread_mutex_unlock(&conn->session->lock);
+end:
+ reply.ret_code = htobe32((uint32_t) reply_code);
+ send_ret = conn->sock->ops->sendmsg(conn->sock,
+ &reply,
+ sizeof(struct lttcomm_relayd_generic_reply),
+ 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"create trace chunk\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+end_no_reply:
+ lttng_trace_chunk_put(chunk);
+ lttng_trace_chunk_put(published_chunk);
+ return ret;
+}
+
+/*
+ * relay_close_trace_chunk: close a trace chunk
+ */
+static int relay_close_trace_chunk(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0, buf_ret;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_close_trace_chunk *msg;
+ struct lttcomm_relayd_close_trace_chunk_reply reply = {};
+ struct lttng_buffer_view header_view;
+ struct lttng_trace_chunk *chunk = NULL;
+ enum lttng_error_code reply_code = LTTNG_OK;
+ enum lttng_trace_chunk_status chunk_status;
+ uint64_t chunk_id;
+ LTTNG_OPTIONAL(enum lttng_trace_chunk_command_type) close_command = {};
+ time_t close_timestamp;
+ char closed_trace_chunk_path[LTTNG_PATH_MAX];
+ size_t path_length = 0;
+ const char *chunk_name = NULL;
+ struct lttng_dynamic_buffer reply_payload;
+ const char *new_path;
+
+ lttng_dynamic_buffer_init(&reply_payload);
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to close a trace chunk before version check");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ if (session->major == 2 && session->minor < 11) {
+ ERR("Chunk close command is unsupported before 2.11");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
+ if (!lttng_buffer_view_is_valid(&header_view)) {
+ ERR("Failed to receive payload of chunk close command");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ /* Convert to host endianness. */
+ msg = (typeof(msg)) header_view.data;
+ chunk_id = be64toh(msg->chunk_id);
+ close_timestamp = (time_t) be64toh(msg->close_timestamp);
+ close_command.value = (lttng_trace_chunk_command_type) be32toh(msg->close_command.value);
+ close_command.is_set = msg->close_command.is_set;
+
+ chunk = sessiond_trace_chunk_registry_get_chunk(
+ sessiond_trace_chunk_registry,
+ conn->session->sessiond_uuid,
+ conn->session->id,
+ chunk_id);
+ if (!chunk) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(conn->session->sessiond_uuid, uuid_str);
+ ERR("Failed to find chunk to close: sessiond_uuid = %s, session_id = %" PRIu64 ", chunk_id = %" PRIu64,
+ uuid_str,
+ conn->session->id,
+ msg->chunk_id);
+ ret = -1;
+ reply_code = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+
+ pthread_mutex_lock(&session->lock);
+ if (close_command.is_set &&
+ close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE) {
+ /*
+ * Clear command. It is a protocol error to ask for a
+ * clear on a relay which does not allow it. Querying
+ * the configuration allows figuring out whether
+ * clearing is allowed before doing the clear.
+ */
+ if (!opt_allow_clear) {
+ ret = -1;
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ goto end_unlock_session;
+ }
+ }
+ if (session->pending_closure_trace_chunk &&
+ session->pending_closure_trace_chunk != chunk) {
+ ERR("Trace chunk close command for session \"%s\" does not target the trace chunk pending closure",
+ session->session_name);
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ ret = -1;
+ goto end_unlock_session;
+ }
+
+ if (session->current_trace_chunk && session->current_trace_chunk != chunk &&
+ !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) {
+ if (close_command.is_set &&
+ close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE &&
+ !session->has_rotated) {
+ /* New chunk stays in session output directory. */
+ new_path = "";
+ } else {
+ /* Use chunk name for new chunk. */
+ new_path = NULL;
+ }
+ /* Rename new chunk path. */
+ chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk,
+ new_path);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end_unlock_session;
+ }
+ session->ongoing_rotation = false;
+ }
+ if ((!close_command.is_set ||
+ close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION) &&
+ !lttng_trace_chunk_get_name_overridden(chunk)) {
+ const char *old_path;
+
+ if (!session->has_rotated) {
+ old_path = "";
+ } else {
+ old_path = NULL;
+ }
+ /* We need to move back the .tmp_old_chunk to its rightful place. */
+ chunk_status = lttng_trace_chunk_rename_path(chunk, old_path);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end_unlock_session;
+ }
+ }
+ chunk_status = lttng_trace_chunk_set_close_timestamp(
+ chunk, close_timestamp);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ERR("Failed to set trace chunk close timestamp");
+ ret = -1;
+ reply_code = LTTNG_ERR_UNK;
+ goto end_unlock_session;
+ }
+
+ if (close_command.is_set) {
+ chunk_status = lttng_trace_chunk_set_close_command(
+ chunk, close_command.value);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ reply_code = LTTNG_ERR_INVALID;
+ goto end_unlock_session;
+ }
+ }
+ chunk_status = lttng_trace_chunk_get_name(chunk, &chunk_name, NULL);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ERR("Failed to get chunk name");
+ ret = -1;
+ reply_code = LTTNG_ERR_UNK;
+ goto end_unlock_session;
+ }
+ if (!session->has_rotated && !session->snapshot) {
+ ret = lttng_strncpy(closed_trace_chunk_path,
+ session->output_path,
+ sizeof(closed_trace_chunk_path));
+ if (ret) {
+ ERR("Failed to send trace chunk path: path length of %zu bytes exceeds the maximal allowed length of %zu bytes",
+ strlen(session->output_path),
+ sizeof(closed_trace_chunk_path));
+ reply_code = LTTNG_ERR_NOMEM;
+ ret = -1;
+ goto end_unlock_session;
+ }
+ } else {
+ if (session->snapshot) {
+ ret = snprintf(closed_trace_chunk_path,
+ sizeof(closed_trace_chunk_path),
+ "%s/%s", session->output_path,
+ chunk_name);
+ } else {
+ ret = snprintf(closed_trace_chunk_path,
+ sizeof(closed_trace_chunk_path),
+ "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY
+ "/%s",
+ session->output_path, chunk_name);
+ }
+ if (ret < 0 || ret == sizeof(closed_trace_chunk_path)) {
+ ERR("Failed to format closed trace chunk resulting path");
+ reply_code = ret < 0 ? LTTNG_ERR_UNK : LTTNG_ERR_NOMEM;
+ ret = -1;
+ goto end_unlock_session;
+ }
+ }
+ if (close_command.is_set &&
+ close_command.value == LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED) {
+ session->has_rotated = true;
+ }
+ DBG("Reply chunk path on close: %s", closed_trace_chunk_path);
+ path_length = strlen(closed_trace_chunk_path) + 1;
+ if (path_length > UINT32_MAX) {
+ ERR("Closed trace chunk path exceeds the maximal length allowed by the protocol");
+ ret = -1;
+ reply_code = LTTNG_ERR_INVALID_PROTOCOL;
+ goto end_unlock_session;
+ }
+
+ if (session->current_trace_chunk == chunk) {
+ /*
+ * After a trace chunk close command, no new streams
+ * referencing the chunk may be created. Hence, on the
+ * event that no new trace chunk have been created for
+ * the session, the reference to the current trace chunk
+ * is released in order to allow it to be reclaimed when
+ * the last stream releases its reference to it.
+ */
+ lttng_trace_chunk_put(session->current_trace_chunk);
+ session->current_trace_chunk = NULL;
+ }
+ lttng_trace_chunk_put(session->pending_closure_trace_chunk);
+ session->pending_closure_trace_chunk = NULL;
+end_unlock_session:
+ pthread_mutex_unlock(&session->lock);
+
+end:
+ reply.generic.ret_code = htobe32((uint32_t) reply_code);
+ reply.path_length = htobe32((uint32_t) path_length);
+ buf_ret = lttng_dynamic_buffer_append(
+ &reply_payload, &reply, sizeof(reply));
+ if (buf_ret) {
+ ERR("Failed to append \"close trace chunk\" command reply header to payload buffer");
+ goto end_no_reply;
+ }
+
+ if (reply_code == LTTNG_OK) {
+ buf_ret = lttng_dynamic_buffer_append(&reply_payload,
+ closed_trace_chunk_path, path_length);
+ if (buf_ret) {
+ ERR("Failed to append \"close trace chunk\" command reply path to payload buffer");
+ goto end_no_reply;
+ }
+ }
+
+ send_ret = conn->sock->ops->sendmsg(conn->sock,
+ reply_payload.data,
+ reply_payload.size,
+ 0);
+ if (send_ret < reply_payload.size) {
+ ERR("Failed to send \"close trace chunk\" command reply of %zu bytes (ret = %zd)",
+ reply_payload.size, send_ret);
+ ret = -1;
+ goto end_no_reply;
+ }
+end_no_reply:
+ lttng_trace_chunk_put(chunk);
+ lttng_dynamic_buffer_reset(&reply_payload);
+ return ret;
+}
+
+/*
+ * relay_trace_chunk_exists: check if a trace chunk exists
+ */
+static int relay_trace_chunk_exists(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ ssize_t send_ret;
+ struct relay_session *session = conn->session;
+ struct lttcomm_relayd_trace_chunk_exists *msg;
+ struct lttcomm_relayd_trace_chunk_exists_reply reply = {};
+ struct lttng_buffer_view header_view;
+ uint64_t chunk_id;
+ bool chunk_exists;
+
+ if (!session || !conn->version_check_done) {
+ ERR("Trying to check for the existance of a trace chunk before version check");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ if (session->major == 2 && session->minor < 11) {
+ ERR("Chunk exists command is unsupported before 2.11");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
+ if (!lttng_buffer_view_is_valid(&header_view)) {
+ ERR("Failed to receive payload of chunk exists command");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ /* Convert to host endianness. */
+ msg = (typeof(msg)) header_view.data;
+ chunk_id = be64toh(msg->chunk_id);
+
+ ret = sessiond_trace_chunk_registry_chunk_exists(
+ sessiond_trace_chunk_registry,
+ conn->session->sessiond_uuid,
+ conn->session->id,
+ chunk_id, &chunk_exists);
+ /*
+ * If ret is not 0, send the reply and report the error to the caller.
+ * It is a protocol (or internal) error and the session/connection
+ * should be torn down.
+ */
+ reply.generic.ret_code = htobe32((uint32_t) (ret == 0 ? LTTNG_OK : LTTNG_ERR_INVALID_PROTOCOL));
+ reply.trace_chunk_exists = ret == 0 ? chunk_exists : 0;
+
+ send_ret = conn->sock->ops->sendmsg(
+ conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"create trace chunk\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+end_no_reply:
+ return ret;
+}
+
+/*
+ * relay_get_configuration: query whether feature is available
+ */
+static int relay_get_configuration(const struct lttcomm_relayd_hdr *recv_hdr,
+ struct relay_connection *conn,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+ ssize_t send_ret;
+ struct lttcomm_relayd_get_configuration *msg;
+ struct lttcomm_relayd_get_configuration_reply reply = {};
+ struct lttng_buffer_view header_view;
+ uint64_t query_flags = 0;
+ uint64_t result_flags = 0;
+
+ header_view = lttng_buffer_view_from_view(payload, 0, sizeof(*msg));
+ if (!lttng_buffer_view_is_valid(&header_view)) {
+ ERR("Failed to receive payload of chunk close command");
+ ret = -1;
+ goto end_no_reply;
+ }
+
+ /* Convert to host endianness. */
+ msg = (typeof(msg)) header_view.data;
+ query_flags = be64toh(msg->query_flags);
+
+ if (query_flags) {
+ ret = LTTNG_ERR_INVALID_PROTOCOL;
+ goto reply;
+ }
+ if (opt_allow_clear) {
+ result_flags |= LTTCOMM_RELAYD_CONFIGURATION_FLAG_CLEAR_ALLOWED;
+ }
+ ret = 0;
+reply:
+ reply.generic.ret_code = htobe32((uint32_t) (ret == 0 ? LTTNG_OK : LTTNG_ERR_INVALID_PROTOCOL));
+ reply.relayd_configuration_flags = htobe64(result_flags);
+
+ send_ret = conn->sock->ops->sendmsg(
+ conn->sock, &reply, sizeof(reply), 0);
+ if (send_ret < (ssize_t) sizeof(reply)) {
+ ERR("Failed to send \"get configuration\" command reply (ret = %zd)",
+ send_ret);
+ ret = -1;
+ }
+end_no_reply:
+ return ret;
+}
+
+#define DBG_CMD(cmd_name, conn) \
+ DBG3("Processing \"%s\" command for socket %i", cmd_name, conn->sock->fd);
+
+static int relay_process_control_command(struct relay_connection *conn,
+ const struct lttcomm_relayd_hdr *header,
+ const struct lttng_buffer_view *payload)
+{
+ int ret = 0;
+
+ switch (header->cmd) {
+ case RELAYD_CREATE_SESSION:
+ DBG_CMD("RELAYD_CREATE_SESSION", conn);
+ ret = relay_create_session(header, conn, payload);
+ break;
+ case RELAYD_ADD_STREAM:
+ DBG_CMD("RELAYD_ADD_STREAM", conn);
+ ret = relay_add_stream(header, conn, payload);
+ break;
+ case RELAYD_START_DATA:
+ DBG_CMD("RELAYD_START_DATA", conn);
+ ret = relay_start(header, conn, payload);
+ break;
+ case RELAYD_SEND_METADATA:
+ DBG_CMD("RELAYD_SEND_METADATA", conn);
+ ret = relay_recv_metadata(header, conn, payload);
+ break;
+ case RELAYD_VERSION:
+ DBG_CMD("RELAYD_VERSION", conn);
+ ret = relay_send_version(header, conn, payload);
+ break;
+ case RELAYD_CLOSE_STREAM:
+ DBG_CMD("RELAYD_CLOSE_STREAM", conn);
+ ret = relay_close_stream(header, conn, payload);
+ break;
+ case RELAYD_DATA_PENDING:
+ DBG_CMD("RELAYD_DATA_PENDING", conn);
+ ret = relay_data_pending(header, conn, payload);
+ break;
+ case RELAYD_QUIESCENT_CONTROL:
+ DBG_CMD("RELAYD_QUIESCENT_CONTROL", conn);
+ ret = relay_quiescent_control(header, conn, payload);
+ break;
+ case RELAYD_BEGIN_DATA_PENDING:
+ DBG_CMD("RELAYD_BEGIN_DATA_PENDING", conn);
+ ret = relay_begin_data_pending(header, conn, payload);
+ break;
+ case RELAYD_END_DATA_PENDING:
+ DBG_CMD("RELAYD_END_DATA_PENDING", conn);
+ ret = relay_end_data_pending(header, conn, payload);
+ break;
+ case RELAYD_SEND_INDEX:
+ DBG_CMD("RELAYD_SEND_INDEX", conn);
+ ret = relay_recv_index(header, conn, payload);
+ break;
+ case RELAYD_STREAMS_SENT:
+ DBG_CMD("RELAYD_STREAMS_SENT", conn);
+ ret = relay_streams_sent(header, conn, payload);
+ break;
+ case RELAYD_RESET_METADATA:
+ DBG_CMD("RELAYD_RESET_METADATA", conn);
+ ret = relay_reset_metadata(header, conn, payload);
+ break;
+ case RELAYD_ROTATE_STREAMS:
+ DBG_CMD("RELAYD_ROTATE_STREAMS", conn);
+ ret = relay_rotate_session_streams(header, conn, payload);
+ break;
+ case RELAYD_CREATE_TRACE_CHUNK:
+ DBG_CMD("RELAYD_CREATE_TRACE_CHUNK", conn);
+ ret = relay_create_trace_chunk(header, conn, payload);
+ break;
+ case RELAYD_CLOSE_TRACE_CHUNK:
+ DBG_CMD("RELAYD_CLOSE_TRACE_CHUNK", conn);
+ ret = relay_close_trace_chunk(header, conn, payload);
+ break;
+ case RELAYD_TRACE_CHUNK_EXISTS:
+ DBG_CMD("RELAYD_TRACE_CHUNK_EXISTS", conn);
+ ret = relay_trace_chunk_exists(header, conn, payload);
+ break;
+ case RELAYD_GET_CONFIGURATION:
+ DBG_CMD("RELAYD_GET_CONFIGURATION", conn);
+ ret = relay_get_configuration(header, conn, payload);
+ break;
+ case RELAYD_UPDATE_SYNC_INFO:
+ default:
+ ERR("Received unknown command (%u)", header->cmd);
+ relay_unknown_command(conn);
+ ret = -1;
+ goto end;
+ }
+
+end:
+ return ret;
+}
+
+static enum relay_connection_status relay_process_control_receive_payload(
+ struct relay_connection *conn)
+{
+ int ret = 0;
+ enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
+ struct lttng_dynamic_buffer *reception_buffer =
+ &conn->protocol.ctrl.reception_buffer;
+ struct ctrl_connection_state_receive_payload *state =
+ &conn->protocol.ctrl.state.receive_payload;
+ struct lttng_buffer_view payload_view;
+
+ if (state->left_to_receive == 0) {
+ /* Short-circuit for payload-less commands. */
+ goto reception_complete;
+ }
+
+ ret = conn->sock->ops->recvmsg(conn->sock,
+ reception_buffer->data + state->received,
+ state->left_to_receive, MSG_DONTWAIT);
+ if (ret < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PERROR("Unable to receive command payload on sock %d",
+ conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ }
+ goto end;
+ } else if (ret == 0) {
+ DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_CLOSED;
+ goto end;
+ }
+
+ LTTNG_ASSERT(ret > 0);
+ LTTNG_ASSERT(ret <= state->left_to_receive);
+
+ state->left_to_receive -= ret;
+ state->received += ret;
+
+ if (state->left_to_receive > 0) {
+ /*
+ * Can't transition to the protocol's next state, wait to
+ * receive the rest of the header.
+ */
+ DBG3("Partial reception of control connection protocol payload (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
+ state->received, state->left_to_receive,
+ conn->sock->fd);
+ goto end;
+ }
+
+reception_complete:
+ DBG("Done receiving control command payload: fd = %i, payload size = %" PRIu64 " bytes",
+ conn->sock->fd, state->received);
+ /*
+ * The payload required to process the command has been received.
+ * A view to the reception buffer is forwarded to the various
+ * commands and the state of the control is reset on success.
+ *
+ * Commands are responsible for sending their reply to the peer.
+ */
+ payload_view = lttng_buffer_view_from_dynamic_buffer(reception_buffer,
+ 0, -1);
+ ret = relay_process_control_command(conn,
+ &state->header, &payload_view);
+ if (ret < 0) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end;
+ }
+
+ ret = connection_reset_protocol_state(conn);
+ if (ret) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ }
+end:
+ return status;
+}
+
+static enum relay_connection_status relay_process_control_receive_header(
+ struct relay_connection *conn)
+{
+ int ret = 0;
+ enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
+ struct lttcomm_relayd_hdr header;
+ struct lttng_dynamic_buffer *reception_buffer =
+ &conn->protocol.ctrl.reception_buffer;
+ struct ctrl_connection_state_receive_header *state =
+ &conn->protocol.ctrl.state.receive_header;
+
+ LTTNG_ASSERT(state->left_to_receive != 0);
+
+ ret = conn->sock->ops->recvmsg(conn->sock,
+ reception_buffer->data + state->received,
+ state->left_to_receive, MSG_DONTWAIT);
+ if (ret < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PERROR("Unable to receive control command header on sock %d",
+ conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ }
+ goto end;
+ } else if (ret == 0) {
+ DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_CLOSED;
+ goto end;
+ }
+
+ LTTNG_ASSERT(ret > 0);
+ LTTNG_ASSERT(ret <= state->left_to_receive);
+
+ state->left_to_receive -= ret;
+ state->received += ret;
+
+ if (state->left_to_receive > 0) {
+ /*
+ * Can't transition to the protocol's next state, wait to
+ * receive the rest of the header.
+ */
+ DBG3("Partial reception of control connection protocol header (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
+ state->received, state->left_to_receive,
+ conn->sock->fd);
+ goto end;
+ }
+
+ /* Transition to next state: receiving the command's payload. */
+ conn->protocol.ctrl.state_id =
+ CTRL_CONNECTION_STATE_RECEIVE_PAYLOAD;
+ memcpy(&header, reception_buffer->data, sizeof(header));
+ header.circuit_id = be64toh(header.circuit_id);
+ header.data_size = be64toh(header.data_size);
+ header.cmd = be32toh(header.cmd);
+ header.cmd_version = be32toh(header.cmd_version);
+ memcpy(&conn->protocol.ctrl.state.receive_payload.header,
+ &header, sizeof(header));
+
+ DBG("Done receiving control command header: fd = %i, cmd = %" PRIu32 ", cmd_version = %" PRIu32 ", payload size = %" PRIu64 " bytes",
+ conn->sock->fd, header.cmd, header.cmd_version,
+ header.data_size);
+
+ if (header.data_size > DEFAULT_NETWORK_RELAYD_CTRL_MAX_PAYLOAD_SIZE) {
+ ERR("Command header indicates a payload (%" PRIu64 " bytes) that exceeds the maximal payload size allowed on a control connection.",
+ header.data_size);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end;
+ }
+
+ conn->protocol.ctrl.state.receive_payload.left_to_receive =
+ header.data_size;
+ conn->protocol.ctrl.state.receive_payload.received = 0;
+ ret = lttng_dynamic_buffer_set_size(reception_buffer,
+ header.data_size);
+ if (ret) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end;
+ }
+
+ if (header.data_size == 0) {
+ /*
+ * Manually invoke the next state as the poll loop
+ * will not wake-up to allow us to proceed further.
+ */
+ status = relay_process_control_receive_payload(conn);
+ }
+end:
+ return status;
+}
+
+/*
+ * Process the commands received on the control socket
+ */
+static enum relay_connection_status relay_process_control(
+ struct relay_connection *conn)
+{
+ enum relay_connection_status status;
+
+ switch (conn->protocol.ctrl.state_id) {
+ case CTRL_CONNECTION_STATE_RECEIVE_HEADER:
+ status = relay_process_control_receive_header(conn);
+ break;
+ case CTRL_CONNECTION_STATE_RECEIVE_PAYLOAD:
+ status = relay_process_control_receive_payload(conn);
+ break;
+ default:
+ ERR("Unknown control connection protocol state encountered.");
+ abort();
+ }
+
+ return status;
+}
+
+static enum relay_connection_status relay_process_data_receive_header(
+ struct relay_connection *conn)
+{
+ int ret;
+ enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
+ struct data_connection_state_receive_header *state =
+ &conn->protocol.data.state.receive_header;
+ struct lttcomm_relayd_data_hdr header;
+ struct relay_stream *stream;
+
+ LTTNG_ASSERT(state->left_to_receive != 0);
+
+ ret = conn->sock->ops->recvmsg(conn->sock,
+ state->header_reception_buffer + state->received,
+ state->left_to_receive, MSG_DONTWAIT);
+ if (ret < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PERROR("Unable to receive data header on sock %d", conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ }
+ goto end;
+ } else if (ret == 0) {
+ /* Orderly shutdown. Not necessary to print an error. */
+ DBG("Socket %d performed an orderly shutdown (received EOF)", conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_CLOSED;
+ goto end;
+ }
+
+ LTTNG_ASSERT(ret > 0);
+ LTTNG_ASSERT(ret <= state->left_to_receive);
+
+ state->left_to_receive -= ret;
+ state->received += ret;
+
+ if (state->left_to_receive > 0) {
+ /*
+ * Can't transition to the protocol's next state, wait to
+ * receive the rest of the header.
+ */
+ DBG3("Partial reception of data connection header (received %" PRIu64 " bytes, %" PRIu64 " bytes left to receive, fd = %i)",
+ state->received, state->left_to_receive,
+ conn->sock->fd);
+ goto end;
+ }
+
+ /* Transition to next state: receiving the payload. */
+ conn->protocol.data.state_id = DATA_CONNECTION_STATE_RECEIVE_PAYLOAD;
+
+ memcpy(&header, state->header_reception_buffer, sizeof(header));
+ header.circuit_id = be64toh(header.circuit_id);
+ header.stream_id = be64toh(header.stream_id);
+ header.data_size = be32toh(header.data_size);
+ header.net_seq_num = be64toh(header.net_seq_num);
+ header.padding_size = be32toh(header.padding_size);
+ memcpy(&conn->protocol.data.state.receive_payload.header, &header, sizeof(header));
+
+ conn->protocol.data.state.receive_payload.left_to_receive =
+ header.data_size;
+ conn->protocol.data.state.receive_payload.received = 0;
+ conn->protocol.data.state.receive_payload.rotate_index = false;
+
+ DBG("Received data connection header on fd %i: circuit_id = %" PRIu64 ", stream_id = %" PRIu64 ", data_size = %" PRIu32 ", net_seq_num = %" PRIu64 ", padding_size = %" PRIu32,
+ conn->sock->fd, header.circuit_id,
+ header.stream_id, header.data_size,
+ header.net_seq_num, header.padding_size);
+
+ stream = stream_get_by_id(header.stream_id);
+ if (!stream) {
+ DBG("relay_process_data_receive_payload: Cannot find stream %" PRIu64,
+ header.stream_id);
+ /* Protocol error. */
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+ /* Prepare stream for the reception of a new packet. */
+ ret = stream_init_packet(stream, header.data_size,
+ &conn->protocol.data.state.receive_payload.rotate_index);
+ pthread_mutex_unlock(&stream->lock);
+ if (ret) {
+ ERR("Failed to rotate stream output file");
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+
+end_stream_unlock:
+ stream_put(stream);
+end:
+ return status;
+}
+
+static enum relay_connection_status relay_process_data_receive_payload(
+ struct relay_connection *conn)
+{
+ int ret;
+ enum relay_connection_status status = RELAY_CONNECTION_STATUS_OK;
+ struct relay_stream *stream;
+ struct data_connection_state_receive_payload *state =
+ &conn->protocol.data.state.receive_payload;
+ const size_t chunk_size = RECV_DATA_BUFFER_SIZE;
+ char data_buffer[chunk_size];
+ bool partial_recv = false;
+ bool new_stream = false, close_requested = false, index_flushed = false;
+ uint64_t left_to_receive = state->left_to_receive;
+ struct relay_session *session;
+
+ DBG3("Receiving data for stream id %" PRIu64 " seqnum %" PRIu64 ", %" PRIu64" bytes received, %" PRIu64 " bytes left to receive",
+ state->header.stream_id, state->header.net_seq_num,
+ state->received, left_to_receive);
+
+ stream = stream_get_by_id(state->header.stream_id);
+ if (!stream) {
+ /* Protocol error. */
+ ERR("relay_process_data_receive_payload: cannot find stream %" PRIu64,
+ state->header.stream_id);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+ session = stream->trace->session;
+ if (!conn->session) {
+ ret = connection_set_session(conn, session);
+ if (ret) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+ }
+
+ /*
+ * The size of the "chunk" received on any iteration is bounded by:
+ * - the data left to receive,
+ * - the data immediately available on the socket,
+ * - the on-stack data buffer
+ */
+ while (left_to_receive > 0 && !partial_recv) {
+ size_t recv_size = std::min(left_to_receive, chunk_size);
+ struct lttng_buffer_view packet_chunk;
+
+ ret = conn->sock->ops->recvmsg(conn->sock, data_buffer,
+ recv_size, MSG_DONTWAIT);
+ if (ret < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PERROR("Socket %d error", conn->sock->fd);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ }
+ goto end_stream_unlock;
+ } else if (ret == 0) {
+ /* No more data ready to be consumed on socket. */
+ DBG3("No more data ready for consumption on data socket of stream id %" PRIu64,
+ state->header.stream_id);
+ status = RELAY_CONNECTION_STATUS_CLOSED;
+ break;
+ } else if (ret < (int) recv_size) {
+ /*
+ * All the data available on the socket has been
+ * consumed.
+ */
+ partial_recv = true;
+ recv_size = ret;
+ }
+
+ packet_chunk = lttng_buffer_view_init(data_buffer,
+ 0, recv_size);
+ LTTNG_ASSERT(packet_chunk.data);
+
+ ret = stream_write(stream, &packet_chunk, 0);
+ if (ret) {
+ ERR("Relay error writing data to file");
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+
+ left_to_receive -= recv_size;
+ state->received += recv_size;
+ state->left_to_receive = left_to_receive;
+ }
+
+ if (state->left_to_receive > 0) {
+ /*
+ * Did not receive all the data expected, wait for more data to
+ * become available on the socket.
+ */
+ DBG3("Partial receive on data connection of stream id %" PRIu64 ", %" PRIu64 " bytes received, %" PRIu64 " bytes left to receive",
+ state->header.stream_id, state->received,
+ state->left_to_receive);
+ goto end_stream_unlock;
+ }
+
+ ret = stream_write(stream, NULL, state->header.padding_size);
+ if (ret) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+
+ if (session_streams_have_index(session)) {
+ ret = stream_update_index(stream, state->header.net_seq_num,
+ state->rotate_index, &index_flushed,
+ state->header.data_size + state->header.padding_size);
+ if (ret < 0) {
+ ERR("Failed to update index: stream %" PRIu64 " net_seq_num %" PRIu64 " ret %d",
+ stream->stream_handle,
+ state->header.net_seq_num, ret);
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+ }
+
+ if (stream->prev_data_seq == -1ULL) {
+ new_stream = true;
+ }
+
+ ret = stream_complete_packet(stream, state->header.data_size +
+ state->header.padding_size, state->header.net_seq_num,
+ index_flushed);
+ if (ret) {
+ status = RELAY_CONNECTION_STATUS_ERROR;
+ goto end_stream_unlock;
+ }
+
+ /*
+ * Resetting the protocol state (to RECEIVE_HEADER) will trash the
+ * contents of *state which are aliased (union) to the same location as
+ * the new state. Don't use it beyond this point.
+ */
+ connection_reset_protocol_state(conn);
+ state = NULL;
+
+end_stream_unlock:
+ close_requested = stream->close_requested;
+ pthread_mutex_unlock(&stream->lock);
+ if (close_requested && left_to_receive == 0) {
+ try_stream_close(stream);
+ }
+
+ if (new_stream) {
+ pthread_mutex_lock(&session->lock);
+ uatomic_set(&session->new_streams, 1);
+ pthread_mutex_unlock(&session->lock);
+ }
+
+ stream_put(stream);
+end:
+ return status;
+}
+
+/*
+ * relay_process_data: Process the data received on the data socket
+ */
+static enum relay_connection_status relay_process_data(
+ struct relay_connection *conn)
+{
+ enum relay_connection_status status;
+
+ switch (conn->protocol.data.state_id) {
+ case DATA_CONNECTION_STATE_RECEIVE_HEADER:
+ status = relay_process_data_receive_header(conn);
+ break;
+ case DATA_CONNECTION_STATE_RECEIVE_PAYLOAD:
+ status = relay_process_data_receive_payload(conn);
+ break;
+ default:
+ ERR("Unexpected data connection communication state.");
+ abort();
+ }
+
+ return status;
+}
+
+static void cleanup_connection_pollfd(struct lttng_poll_event *events, int pollfd)
+{
+ int ret;
+
+ (void) lttng_poll_del(events, pollfd);
+
+ ret = fd_tracker_close_unsuspendable_fd(the_fd_tracker, &pollfd, 1,
+ fd_tracker_util_close_fd, NULL);
+ if (ret < 0) {
+ ERR("Closing pollfd %d", pollfd);
+ }
+}
+
+static void relay_thread_close_connection(struct lttng_poll_event *events,
+ int pollfd, struct relay_connection *conn)
+{
+ const char *type_str;
+
+ switch (conn->type) {
+ case RELAY_DATA:
+ type_str = "Data";
+ break;
+ case RELAY_CONTROL:
+ type_str = "Control";
+ break;
+ case RELAY_VIEWER_COMMAND:
+ type_str = "Viewer Command";
+ break;
+ case RELAY_VIEWER_NOTIFICATION:
+ type_str = "Viewer Notification";
+ break;
+ default:
+ type_str = "Unknown";
+ }
+ cleanup_connection_pollfd(events, pollfd);
+ connection_put(conn);
+ DBG("%s connection closed with %d", type_str, pollfd);
+}
+
+/*
+ * This thread does the actual work
+ */
+static void *relay_thread_worker(void *data)
+{
+ int ret, err = -1, last_seen_data_fd = -1;
+ uint32_t nb_fd;
+ struct lttng_poll_event events;
+ struct lttng_ht *relay_connections_ht;
+ struct lttng_ht_iter iter;
+ struct relay_connection *destroy_conn = NULL;
+
+ DBG("[thread] Relay worker started");
+
+ rcu_register_thread();
+
+ health_register(health_relayd, HEALTH_RELAYD_TYPE_WORKER);
+
+ if (testpoint(relayd_thread_worker)) {
+ goto error_testpoint;
+ }
+
+ health_code_update();
+
+ /* table of connections indexed on socket */
+ relay_connections_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG);
+ if (!relay_connections_ht) {
+ goto relay_connections_ht_error;
+ }
+
+ ret = create_named_thread_poll_set(&events, 2, "Worker thread epoll");
+ if (ret < 0) {
+ goto error_poll_create;
+ }
+
+ ret = lttng_poll_add(&events, relay_conn_pipe[0], LPOLLIN | LPOLLRDHUP);
+ if (ret < 0) {
+ goto error;
+ }
+
+restart:
+ while (1) {
+ int idx = -1, i, seen_control = 0, last_notdel_data_fd = -1;
+
+ health_code_update();
+
+ /* Infinite blocking call, waiting for transmission */
+ DBG3("Relayd worker thread polling...");
+ health_poll_entry();
+ ret = lttng_poll_wait(&events, -1);
+ health_poll_exit();
+ if (ret < 0) {
+ /*
+ * Restart interrupted system call.
+ */
+ if (errno == EINTR) {
+ goto restart;
+ }
+ goto error;
+ }
+
+ nb_fd = ret;
+
+ /*
+ * Process control. The control connection is
+ * prioritized so we don't starve it with high
+ * throughput tracing data on the data connection.
+ */
+ for (i = 0; i < nb_fd; i++) {
+ /* Fetch once the poll data */
+ uint32_t revents = LTTNG_POLL_GETEV(&events, i);
+ int pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ health_code_update();
+
+ /* Thread quit pipe has been closed. Killing thread. */
+ ret = check_thread_quit_pipe(pollfd, revents);
+ if (ret) {
+ err = 0;
+ goto exit;
+ }
+
+ /* Inspect the relay conn pipe for new connection */
+ if (pollfd == relay_conn_pipe[0]) {
+ if (revents & LPOLLIN) {
+ struct relay_connection *conn;
+
+ ret = lttng_read(relay_conn_pipe[0], &conn, sizeof(conn));
+ if (ret < 0) {
+ goto error;
+ }
+ ret = lttng_poll_add(&events,
+ conn->sock->fd,
+ LPOLLIN | LPOLLRDHUP);
+ if (ret) {
+ ERR("Failed to add new connection file descriptor to poll set");
+ goto error;
+ }
+ connection_ht_add(relay_connections_ht, conn);
+ DBG("Connection socket %d added", conn->sock->fd);
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ ERR("Relay connection pipe error");
+ goto error;
+ } else {
+ ERR("Unexpected poll events %u for sock %d", revents, pollfd);
+ goto error;
+ }
+ } else {
+ struct relay_connection *ctrl_conn;
+
+ ctrl_conn = connection_get_by_sock(relay_connections_ht, pollfd);
+ /* If not found, there is a synchronization issue. */
+ LTTNG_ASSERT(ctrl_conn);
+
+ if (ctrl_conn->type == RELAY_DATA) {
+ if (revents & LPOLLIN) {
+ /*
+ * Flag the last seen data fd not deleted. It will be
+ * used as the last seen fd if any fd gets deleted in
+ * this first loop.
+ */
+ last_notdel_data_fd = pollfd;
+ }
+ goto put_ctrl_connection;
+ }
+ LTTNG_ASSERT(ctrl_conn->type == RELAY_CONTROL);
+
+ if (revents & LPOLLIN) {
+ enum relay_connection_status status;
+
+ status = relay_process_control(ctrl_conn);
+ if (status != RELAY_CONNECTION_STATUS_OK) {
+ /*
+ * On socket error flag the session as aborted to force
+ * the cleanup of its stream otherwise it can leak
+ * during the lifetime of the relayd.
+ *
+ * This prevents situations in which streams can be
+ * left opened because an index was received, the
+ * control connection is closed, and the data
+ * connection is closed (uncleanly) before the packet's
+ * data provided.
+ *
+ * Since the control connection encountered an error,
+ * it is okay to be conservative and close the
+ * session right now as we can't rely on the protocol
+ * being respected anymore.
+ */
+ if (status == RELAY_CONNECTION_STATUS_ERROR) {
+ session_abort(ctrl_conn->session);
+ }
+
+ /* Clear the connection on error or close. */
+ relay_thread_close_connection(&events,
+ pollfd,
+ ctrl_conn);
+ }
+ seen_control = 1;
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ relay_thread_close_connection(&events,
+ pollfd, ctrl_conn);
+ if (last_seen_data_fd == pollfd) {
+ last_seen_data_fd = last_notdel_data_fd;
+ }
+ } else {
+ ERR("Unexpected poll events %u for control sock %d",
+ revents, pollfd);
+ connection_put(ctrl_conn);
+ goto error;
+ }
+ put_ctrl_connection:
+ connection_put(ctrl_conn);
+ }
+ }
+
+ /*
+ * The last loop handled a control request, go back to poll to make
+ * sure we prioritise the control socket.
+ */
+ if (seen_control) {
+ continue;
+ }
+
+ if (last_seen_data_fd >= 0) {
+ for (i = 0; i < nb_fd; i++) {
+ int pollfd = LTTNG_POLL_GETFD(&events, i);
+
+ health_code_update();
+
+ if (last_seen_data_fd == pollfd) {
+ idx = i;
+ break;
+ }
+ }
+ }
+
+ /* Process data connection. */
+ for (i = idx + 1; i < nb_fd; i++) {
+ /* Fetch the poll data. */
+ uint32_t revents = LTTNG_POLL_GETEV(&events, i);
+ int pollfd = LTTNG_POLL_GETFD(&events, i);
+ struct relay_connection *data_conn;
+
+ health_code_update();
+
+ if (!revents) {
+ /* No activity for this FD (poll implementation). */
+ continue;
+ }
+
+ /* Skip the command pipe. It's handled in the first loop. */
+ if (pollfd == relay_conn_pipe[0]) {
+ continue;
+ }
+
+ data_conn = connection_get_by_sock(relay_connections_ht, pollfd);
+ if (!data_conn) {
+ /* Skip it. Might be removed before. */
+ continue;
+ }
+ if (data_conn->type == RELAY_CONTROL) {
+ goto put_data_connection;
+ }
+ LTTNG_ASSERT(data_conn->type == RELAY_DATA);
+
+ if (revents & LPOLLIN) {
+ enum relay_connection_status status;
+
+ status = relay_process_data(data_conn);
+ /* Connection closed or error. */
+ if (status != RELAY_CONNECTION_STATUS_OK) {
+ /*
+ * On socket error flag the session as aborted to force
+ * the cleanup of its stream otherwise it can leak
+ * during the lifetime of the relayd.
+ *
+ * This prevents situations in which streams can be
+ * left opened because an index was received, the
+ * control connection is closed, and the data
+ * connection is closed (uncleanly) before the packet's
+ * data provided.
+ *
+ * Since the data connection encountered an error,
+ * it is okay to be conservative and close the
+ * session right now as we can't rely on the protocol
+ * being respected anymore.
+ */
+ if (status == RELAY_CONNECTION_STATUS_ERROR) {
+ session_abort(data_conn->session);
+ }
+ relay_thread_close_connection(&events, pollfd,
+ data_conn);
+ /*
+ * Every goto restart call sets the last seen fd where
+ * here we don't really care since we gracefully
+ * continue the loop after the connection is deleted.
+ */
+ } else {
+ /* Keep last seen port. */
+ last_seen_data_fd = pollfd;
+ connection_put(data_conn);
+ goto restart;
+ }
+ } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) {
+ relay_thread_close_connection(&events, pollfd,
+ data_conn);
+ } else {
+ ERR("Unknown poll events %u for data sock %d",
+ revents, pollfd);
+ }
+ put_data_connection:
+ connection_put(data_conn);
+ }
+ last_seen_data_fd = -1;
+ }
+
+ /* Normal exit, no error */
+ ret = 0;
+
+exit:
+error:
+ /* Cleanup remaining connection object. */
+ rcu_read_lock();
+ cds_lfht_for_each_entry(relay_connections_ht->ht, &iter.iter,
+ destroy_conn,
+ sock_n.node) {
+ health_code_update();
+
+ session_abort(destroy_conn->session);
+
+ /*
+ * No need to grab another ref, because we own
+ * destroy_conn.
+ */
+ relay_thread_close_connection(&events, destroy_conn->sock->fd,
+ destroy_conn);
+ }
+ rcu_read_unlock();
+
+ (void) fd_tracker_util_poll_clean(the_fd_tracker, &events);
+error_poll_create:
+ lttng_ht_destroy(relay_connections_ht);
+relay_connections_ht_error:
+ /* Close relay conn pipes */
+ (void) fd_tracker_util_pipe_close(the_fd_tracker,
+ relay_conn_pipe);
+ if (err) {
+ DBG("Thread exited with error");
+ }
+ DBG("Worker thread cleanup complete");
+error_testpoint:
+ if (err) {
+ health_error();
+ ERR("Health error occurred in %s", __func__);
+ }
+ health_unregister(health_relayd);
+ rcu_unregister_thread();
+ lttng_relay_stop_threads();
+ return NULL;
+}
+
+/*
+ * Create the relay command pipe to wake thread_manage_apps.
+ * Closed in cleanup().
+ */
+static int create_relay_conn_pipe(void)
+{
+ return fd_tracker_util_pipe_open_cloexec(the_fd_tracker,
+ "Relayd connection pipe", relay_conn_pipe);
+}
+
+static int stdio_open(void *data, int *fds)
+{
+ fds[0] = fileno(stdout);
+ fds[1] = fileno(stderr);
+ return 0;
+}
+
+static int track_stdio(void)
+{
+ int fds[2];
+ const char *names[] = { "stdout", "stderr" };
+
+ return fd_tracker_open_unsuspendable_fd(the_fd_tracker, fds,
+ names, 2, stdio_open, NULL);
+}
+
+/*
+ * main
+ */
+int main(int argc, char **argv)
+{
+ bool thread_is_rcu_registered = false;
+ int ret = 0, retval = 0;
+ void *status;
+ char *unlinked_file_directory_path = NULL, *output_path = NULL;
+
+ /* Parse environment variables */
+ ret = parse_env_options();
+ if (ret) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /*
+ * Parse arguments.
+ * Command line arguments overwrite environment.
+ */
+ progname = argv[0];
+ if (set_options(argc, argv)) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ if (set_signal_handler()) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ relayd_config_log();
+
+ if (opt_print_version) {
+ print_version();
+ retval = 0;
+ goto exit_options;
+ }
+
+ ret = fclose(stdin);
+ if (ret) {
+ PERROR("Failed to close stdin");
+ goto exit_options;
+ }
+
+ DBG("Clear command %s", opt_allow_clear ? "allowed" : "disallowed");
+
+ /* Try to create directory if -o, --output is specified. */
+ if (opt_output_path) {
+ if (*opt_output_path != '/') {
+ ERR("Please specify an absolute path for -o, --output PATH");
+ retval = -1;
+ goto exit_options;
+ }
+
+ ret = utils_mkdir_recursive(opt_output_path, S_IRWXU | S_IRWXG,
+ -1, -1);
+ if (ret < 0) {
+ ERR("Unable to create %s", opt_output_path);
+ retval = -1;
+ goto exit_options;
+ }
+ }
+
+ /* Daemonize */
+ if (opt_daemon || opt_background) {
+ ret = lttng_daemonize(&child_ppid, &recv_child_signal,
+ !opt_background);
+ if (ret < 0) {
+ retval = -1;
+ goto exit_options;
+ }
+ }
+
+ if (opt_working_directory) {
+ ret = utils_change_working_directory(opt_working_directory);
+ if (ret) {
+ /* All errors are already logged. */
+ goto exit_options;
+ }
+ }
+
+ sessiond_trace_chunk_registry = sessiond_trace_chunk_registry_create();
+ if (!sessiond_trace_chunk_registry) {
+ ERR("Failed to initialize session daemon trace chunk registry");
+ retval = -1;
+ goto exit_options;
+ }
+
+ /*
+ * The RCU thread registration (and use, through the fd-tracker's
+ * creation) is done after the daemonization to allow us to not
+ * deal with liburcu's fork() management as the call RCU needs to
+ * be restored.
+ */
+ rcu_register_thread();
+ thread_is_rcu_registered = true;
+
+ output_path = create_output_path("");
+ if (!output_path) {
+ ERR("Failed to get output path");
+ retval = -1;
+ goto exit_options;
+ }
+ ret = asprintf(&unlinked_file_directory_path, "%s/%s", output_path,
+ DEFAULT_UNLINKED_FILES_DIRECTORY);
+ free(output_path);
+ if (ret < 0) {
+ ERR("Failed to format unlinked file directory path");
+ retval = -1;
+ goto exit_options;
+ }
+ the_fd_tracker = fd_tracker_create(
+ unlinked_file_directory_path, lttng_opt_fd_pool_size);
+ free(unlinked_file_directory_path);
+ if (!the_fd_tracker) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ ret = track_stdio();
+ if (ret) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Initialize thread health monitoring */
+ health_relayd = health_app_create(NR_HEALTH_RELAYD_TYPES);
+ if (!health_relayd) {
+ PERROR("health_app_create error");
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Create thread quit pipe */
+ if (init_thread_quit_pipe()) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Setup the thread apps communication pipe. */
+ if (create_relay_conn_pipe()) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Init relay command queue. */
+ cds_wfcq_init(&relay_conn_queue.head, &relay_conn_queue.tail);
+
+ /* Initialize communication library */
+ lttcomm_init();
+ lttcomm_inet_init();
+
+ /* tables of sessions indexed by session ID */
+ sessions_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
+ if (!sessions_ht) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* tables of streams indexed by stream ID */
+ relay_streams_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
+ if (!relay_streams_ht) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* tables of streams indexed by stream ID */
+ viewer_streams_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
+ if (!viewer_streams_ht) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ ret = init_health_quit_pipe();
+ if (ret) {
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Create thread to manage the client socket */
+ ret = pthread_create(&health_thread, default_pthread_attr(),
+ thread_manage_health, (void *) NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create health");
+ retval = -1;
+ goto exit_options;
+ }
+
+ /* Setup the dispatcher thread */
+ ret = pthread_create(&dispatcher_thread, default_pthread_attr(),
+ relay_thread_dispatcher, (void *) NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create dispatcher");
+ retval = -1;
+ goto exit_dispatcher_thread;
+ }
+
+ /* Setup the worker thread */
+ ret = pthread_create(&worker_thread, default_pthread_attr(),
+ relay_thread_worker, NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create worker");
+ retval = -1;
+ goto exit_worker_thread;
+ }
+
+ /* Setup the listener thread */
+ ret = pthread_create(&listener_thread, default_pthread_attr(),
+ relay_thread_listener, (void *) NULL);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_create listener");
+ retval = -1;
+ goto exit_listener_thread;
+ }
+
+ ret = relayd_live_create(live_uri);
+ if (ret) {
+ ERR("Starting live viewer threads");
+ retval = -1;
+ goto exit_live;
+ }
+
+ /*
+ * This is where we start awaiting program completion (e.g. through
+ * signal that asks threads to teardown).
+ */
+
+ ret = relayd_live_join();
+ if (ret) {
+ retval = -1;
+ }
+exit_live:
+
+ ret = pthread_join(listener_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join listener_thread");
+ retval = -1;
+ }
+
+exit_listener_thread:
+ ret = pthread_join(worker_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join worker_thread");
+ retval = -1;
+ }
+
+exit_worker_thread:
+ ret = pthread_join(dispatcher_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join dispatcher_thread");
+ retval = -1;
+ }
+exit_dispatcher_thread:
+
+ ret = pthread_join(health_thread, &status);
+ if (ret) {
+ errno = ret;
+ PERROR("pthread_join health_thread");
+ retval = -1;
+ }
+exit_options:
+ /*
+ * Wait for all pending call_rcu work to complete before tearing
+ * down data structures. call_rcu worker may be trying to
+ * perform lookups in those structures.
+ */
+ rcu_barrier();
+ relayd_cleanup();
+
+ /* Ensure all prior call_rcu are done. */
+ rcu_barrier();
+
+ if (thread_is_rcu_registered) {
+ rcu_unregister_thread();
+ }
+
+ if (!retval) {
+ exit(EXIT_SUCCESS);
+ } else {
+ exit(EXIT_FAILURE);
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <common/compat/path.h>
-#include <common/fd-tracker/utils.h>
-#include <common/time.h>
-#include <common/utils.h>
-#include <common/uuid.h>
-#include <urcu/rculist.h>
-
-#include <sys/stat.h>
-
-#include "ctf-trace.h"
-#include "lttng-relayd.h"
-#include "session.h"
-#include "sessiond-trace-chunks.h"
-#include "stream.h"
-#include <common/defaults.h>
-#include "utils.h"
-
-/* Global session id used in the session creation. */
-static uint64_t last_relay_session_id;
-static pthread_mutex_t last_relay_session_id_lock = PTHREAD_MUTEX_INITIALIZER;
-
-static int init_session_output_path_group_by_host(struct relay_session *session)
-{
- /*
- * session_directory:
- *
- * if base_path is \0'
- * hostname/session_name
- * else
- * hostname/base_path
- */
- char *session_directory = NULL;
- int ret = 0;
-
- if (session->output_path[0] != '\0') {
- goto end;
- }
- /*
- * If base path is set, it overrides the session name for the
- * session relative base path. No timestamp is appended if the
- * base path is overridden.
- *
- * If the session name already contains the creation time (e.g.
- * auto-<timestamp>, don't append yet another timestamp after
- * the session name in the generated path.
- *
- * Otherwise, generate the path with session_name-<timestamp>.
- */
- if (session->base_path[0] != '\0') {
- ret = asprintf(&session_directory, "%s/%s", session->hostname,
- session->base_path);
- } else if (session->session_name_contains_creation_time) {
- ret = asprintf(&session_directory, "%s/%s", session->hostname,
- session->session_name);
- } else {
- char session_creation_datetime[DATETIME_STR_LEN];
-
- ret = time_to_datetime_str(
- LTTNG_OPTIONAL_GET(session->creation_time),
- session_creation_datetime,
- sizeof(session_creation_datetime));
- if (ret) {
- ERR("Failed to format session creation timestamp while initializing session output directory handle");
- ret = -1;
- goto end;
- }
-
- ret = asprintf(&session_directory, "%s/%s-%s",
- session->hostname, session->session_name,
- session_creation_datetime);
- }
- if (ret < 0) {
- PERROR("Failed to format session directory name");
- goto end;
- }
-
- if (strlen(session_directory) >= LTTNG_PATH_MAX) {
- ERR("Session output directory exceeds maximal length");
- ret = -1;
- goto end;
- }
- strcpy(session->output_path, session_directory);
- ret = 0;
-
-end:
- free(session_directory);
- return ret;
-}
-
-static int init_session_output_path_group_by_session(
- struct relay_session *session)
-{
- /*
- * session_directory:
- *
- * session_name/hostname-creation_time/base_path
- *
- * For session name including the datetime, use it as the complete name
- * since. Do not perform modification on it since the datetime is an
- * integral part of the name and how a user identify a session.
- */
- int ret = 0;
- char *session_directory = NULL;
- char creation_datetime[DATETIME_STR_LEN];
-
- if (session->output_path[0] != '\0') {
- /* output_path as been generated already */
- goto end;
- }
-
- ret = time_to_datetime_str(LTTNG_OPTIONAL_GET(session->creation_time),
- creation_datetime, sizeof(creation_datetime));
- if (ret) {
- ERR("Failed to format session creation timestamp while initializing session output directory handle");
- ret = -1;
- goto end;
- }
-
- ret = asprintf(&session_directory, "%s/%s-%s%s%s",
- session->session_name, session->hostname,
- creation_datetime,
- session->base_path[0] != '\0' ? "/" : "",
- session->base_path);
- if (ret < 0) {
- PERROR("Failed to format session directory name");
- goto end;
- }
-
- if (strlen(session_directory) >= LTTNG_PATH_MAX) {
- ERR("Session output directory exceeds maximal length");
- ret = -1;
- goto end;
- }
-
- strcpy(session->output_path, session_directory);
- ret = 0;
-
-end:
- free(session_directory);
- return ret;
-}
-
-static int init_session_output_path(struct relay_session *session)
-{
- int ret;
-
- switch (opt_group_output_by) {
- case RELAYD_GROUP_OUTPUT_BY_HOST:
- ret = init_session_output_path_group_by_host(session);
- break;
- case RELAYD_GROUP_OUTPUT_BY_SESSION:
- ret = init_session_output_path_group_by_session(session);
- break;
- case RELAYD_GROUP_OUTPUT_BY_UNKNOWN:
- default:
- abort();
- break;
- }
-
- return ret;
-}
-
-static struct lttng_directory_handle *session_create_output_directory_handle(
- struct relay_session *session)
-{
- int ret;
- /*
- * relayd_output_path/session_directory
- * e.g. /home/user/lttng-traces/hostname/session_name
- */
- char *full_session_path = NULL;
- struct lttng_directory_handle *handle = NULL;
-
- pthread_mutex_lock(&session->lock);
- full_session_path = create_output_path(session->output_path);
- if (!full_session_path) {
- goto end;
- }
-
- ret = utils_mkdir_recursive(
- full_session_path, S_IRWXU | S_IRWXG, -1, -1);
- if (ret) {
- ERR("Failed to create session output path \"%s\"",
- full_session_path);
- goto end;
- }
-
- handle = fd_tracker_create_directory_handle(the_fd_tracker, full_session_path);
-end:
- pthread_mutex_unlock(&session->lock);
- free(full_session_path);
- return handle;
-}
-
-static int session_set_anonymous_chunk(struct relay_session *session)
-{
- int ret = 0;
- struct lttng_trace_chunk *chunk = NULL;
- enum lttng_trace_chunk_status status;
- struct lttng_directory_handle *output_directory;
-
- output_directory = session_create_output_directory_handle(session);
- if (!output_directory) {
- goto end;
- }
-
- chunk = lttng_trace_chunk_create_anonymous();
- if (!chunk) {
- goto end;
- }
-
- lttng_trace_chunk_set_fd_tracker(chunk, the_fd_tracker);
- status = lttng_trace_chunk_set_credentials_current_user(chunk);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
-
- status = lttng_trace_chunk_set_as_owner(chunk, output_directory);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
-
- session->current_trace_chunk = chunk;
- chunk = NULL;
-end:
- lttng_trace_chunk_put(chunk);
- lttng_directory_handle_put(output_directory);
- return ret;
-}
-
-/*
- * Check if a name is safe to use in a path.
- *
- * A name that is deemed "path-safe":
- * - Does not contains a path separator (/ or \, platform dependant),
- * - Does not start with a '.' (hidden file/folder),
- * - Is not empty.
- */
-static bool is_name_path_safe(const char *name)
-{
- const size_t name_len = strlen(name);
-
- /* Not empty. */
- if (name_len == 0) {
- WARN("An empty name is not allowed to be used in a path");
- return false;
- }
- /* Does not start with '.'. */
- if (name[0] == '.') {
- WARN("Name \"%s\" is not allowed to be used in a path since it starts with '.'", name);
- return false;
- }
- /* Does not contain a path-separator. */
- if (strchr(name, LTTNG_PATH_SEPARATOR)) {
- WARN("Name \"%s\" is not allowed to be used in a path since it contains a path separator", name);
- return false;
- }
-
- return true;
-}
-
-/*
- * Create a new session by assigning a new session ID.
- *
- * Return allocated session or else NULL.
- */
-struct relay_session *session_create(const char *session_name,
- const char *hostname, const char *base_path,
- uint32_t live_timer,
- bool snapshot,
- const lttng_uuid sessiond_uuid,
- const uint64_t *id_sessiond,
- const uint64_t *current_chunk_id,
- const time_t *creation_time,
- uint32_t major,
- uint32_t minor,
- bool session_name_contains_creation_time)
-{
- int ret;
- struct relay_session *session = NULL;
-
- LTTNG_ASSERT(session_name);
- LTTNG_ASSERT(hostname);
- LTTNG_ASSERT(base_path);
-
- if (!is_name_path_safe(session_name)) {
- ERR("Refusing to create session as the provided session name is not path-safe");
- goto error;
- }
- if (!is_name_path_safe(hostname)) {
- ERR("Refusing to create session as the provided hostname is not path-safe");
- goto error;
- }
- if (strstr(base_path, "../")) {
- ERR("Invalid session base path walks up the path hierarchy: \"%s\"",
- base_path);
- goto error;
- }
-
- session = zmalloc(sizeof(*session));
- if (!session) {
- PERROR("Failed to allocate session");
- goto error;
- }
-
- pthread_mutex_lock(&last_relay_session_id_lock);
- session->id = ++last_relay_session_id;
- pthread_mutex_unlock(&last_relay_session_id_lock);
-
- lttng_ht_node_init_u64(&session->session_n, session->id);
- urcu_ref_init(&session->ref);
- CDS_INIT_LIST_HEAD(&session->recv_list);
- pthread_mutex_init(&session->lock, NULL);
- pthread_mutex_init(&session->recv_list_lock, NULL);
-
- if (lttng_strncpy(session->session_name, session_name,
- sizeof(session->session_name))) {
- WARN("Session name exceeds maximal allowed length");
- goto error;
- }
- if (lttng_strncpy(session->hostname, hostname,
- sizeof(session->hostname))) {
- WARN("Hostname exceeds maximal allowed length");
- goto error;
- }
- if (lttng_strncpy(session->base_path, base_path,
- sizeof(session->base_path))) {
- WARN("Base path exceeds maximal allowed length");
- goto error;
- }
- if (creation_time) {
- LTTNG_OPTIONAL_SET(&session->creation_time, *creation_time);
- } else {
- LTTNG_OPTIONAL_SET(&session->creation_time, time(NULL));
- if (session->creation_time.value == (time_t) -1) {
- PERROR("Failed to sample session creation time");
- goto error;
- }
- }
- session->session_name_contains_creation_time =
- session_name_contains_creation_time;
-
- session->ctf_traces_ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING);
- if (!session->ctf_traces_ht) {
- goto error;
- }
-
- session->major = major;
- session->minor = minor;
-
- session->live_timer = live_timer;
- session->snapshot = snapshot;
- lttng_uuid_copy(session->sessiond_uuid, sessiond_uuid);
-
- if (id_sessiond) {
- LTTNG_OPTIONAL_SET(&session->id_sessiond, *id_sessiond);
- }
-
- if (major == 2 && minor >= 11) {
- /* Only applies for 2.11+ peers using trace chunks. */
- ret = init_session_output_path(session);
- if (ret) {
- goto error;
- }
- }
-
- ret = sessiond_trace_chunk_registry_session_created(
- sessiond_trace_chunk_registry, sessiond_uuid);
- if (ret) {
- goto error;
- }
-
- if (id_sessiond && current_chunk_id) {
- enum lttng_trace_chunk_status chunk_status;
- struct lttng_directory_handle *session_output_directory;
-
- session->current_trace_chunk =
- sessiond_trace_chunk_registry_get_chunk(
- sessiond_trace_chunk_registry,
- session->sessiond_uuid,
- session->id_sessiond.value,
- *current_chunk_id);
- if (!session->current_trace_chunk) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
- ERR("Could not find trace chunk: sessiond = {%s}, sessiond session id = %" PRIu64 ", trace chunk id = %" PRIu64,
- uuid_str, *id_sessiond,
- *current_chunk_id);
- goto error;
- }
-
- chunk_status = lttng_trace_chunk_get_session_output_directory_handle(
- session->current_trace_chunk,
- &session_output_directory);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- goto error;
- }
-
- LTTNG_ASSERT(session_output_directory);
- session->output_directory = session_output_directory;
- } else if (!id_sessiond) {
- /*
- * Pre-2.11 peers will not announce trace chunks. An
- * anonymous trace chunk which will remain set for the
- * duration of the session is created.
- */
- ret = session_set_anonymous_chunk(session);
- if (ret) {
- goto error;
- }
- } else {
- session->output_directory =
- session_create_output_directory_handle(session);
- if (!session->output_directory) {
- goto error;
- }
- }
-
- lttng_ht_add_unique_u64(sessions_ht, &session->session_n);
- return session;
-
-error:
- session_put(session);
- return NULL;
-}
-
-/* Should be called with RCU read-side lock held. */
-bool session_get(struct relay_session *session)
-{
- return urcu_ref_get_unless_zero(&session->ref);
-}
-
-/*
- * Lookup a session within the session hash table using the session id
- * as key. A session reference is taken when a session is returned.
- * session_put() must be called on that session.
- *
- * Return session or NULL if not found.
- */
-struct relay_session *session_get_by_id(uint64_t id)
-{
- struct relay_session *session = NULL;
- struct lttng_ht_node_u64 *node;
- struct lttng_ht_iter iter;
-
- rcu_read_lock();
- lttng_ht_lookup(sessions_ht, &id, &iter);
- node = lttng_ht_iter_get_node_u64(&iter);
- if (!node) {
- DBG("Session find by ID %" PRIu64 " id NOT found", id);
- goto end;
- }
- session = caa_container_of(node, struct relay_session, session_n);
- DBG("Session find by ID %" PRIu64 " id found", id);
- if (!session_get(session)) {
- session = NULL;
- }
-end:
- rcu_read_unlock();
- return session;
-}
-
-static void rcu_destroy_session(struct rcu_head *rcu_head)
-{
- struct relay_session *session =
- caa_container_of(rcu_head, struct relay_session,
- rcu_node);
- /*
- * Since each trace has a reference on the session, it means
- * that if we are at the point where we teardown the session, no
- * trace belonging to that session exist at this point.
- * Calling lttng_ht_destroy in call_rcu worker thread so we
- * don't hold the RCU read-side lock while calling it.
- */
- lttng_ht_destroy(session->ctf_traces_ht);
- free(session);
-}
-
-/*
- * Delete session from the given hash table.
- *
- * Return lttng ht del error code being 0 on success and 1 on failure.
- */
-static int session_delete(struct relay_session *session)
-{
- struct lttng_ht_iter iter;
-
- iter.iter.node = &session->session_n.node;
- return lttng_ht_del(sessions_ht, &iter);
-}
-
-
-static void destroy_session(struct relay_session *session)
-{
- int ret;
-
- ret = session_delete(session);
- LTTNG_ASSERT(!ret);
- lttng_trace_chunk_put(session->current_trace_chunk);
- session->current_trace_chunk = NULL;
- lttng_trace_chunk_put(session->pending_closure_trace_chunk);
- session->pending_closure_trace_chunk = NULL;
- ret = sessiond_trace_chunk_registry_session_destroyed(
- sessiond_trace_chunk_registry, session->sessiond_uuid);
- LTTNG_ASSERT(!ret);
- lttng_directory_handle_put(session->output_directory);
- session->output_directory = NULL;
- call_rcu(&session->rcu_node, rcu_destroy_session);
-}
-
-static void session_release(struct urcu_ref *ref)
-{
- struct relay_session *session =
- caa_container_of(ref, struct relay_session, ref);
-
- destroy_session(session);
-}
-
-void session_put(struct relay_session *session)
-{
- if (!session) {
- return;
- }
- rcu_read_lock();
- urcu_ref_put(&session->ref, session_release);
- rcu_read_unlock();
-}
-
-int session_close(struct relay_session *session)
-{
- int ret = 0;
- struct ctf_trace *trace;
- struct lttng_ht_iter iter;
- struct relay_stream *stream;
-
- pthread_mutex_lock(&session->lock);
- DBG("closing session %" PRIu64 ": is conn already closed %d",
- session->id, session->connection_closed);
- session->connection_closed = true;
- pthread_mutex_unlock(&session->lock);
-
- rcu_read_lock();
- cds_lfht_for_each_entry(session->ctf_traces_ht->ht,
- &iter.iter, trace, node.node) {
- ret = ctf_trace_close(trace);
- if (ret) {
- goto rcu_unlock;
- }
- }
- cds_list_for_each_entry_rcu(stream, &session->recv_list,
- recv_node) {
- /* Close streams which have not been published yet. */
- try_stream_close(stream);
- }
-rcu_unlock:
- rcu_read_unlock();
- if (ret) {
- return ret;
- }
- /* Put self-reference from create. */
- session_put(session);
- return ret;
-}
-
-int session_abort(struct relay_session *session)
-{
- int ret = 0;
-
- if (!session) {
- return 0;
- }
-
- pthread_mutex_lock(&session->lock);
- DBG("aborting session %" PRIu64, session->id);
- session->aborted = true;
- pthread_mutex_unlock(&session->lock);
- return ret;
-}
-
-void print_sessions(void)
-{
- struct lttng_ht_iter iter;
- struct relay_session *session;
-
- if (!sessions_ht) {
- return;
- }
-
- rcu_read_lock();
- cds_lfht_for_each_entry(sessions_ht->ht, &iter.iter, session,
- session_n.node) {
- if (!session_get(session)) {
- continue;
- }
- DBG("session %p refcount %ld session %" PRIu64,
- session,
- session->ref.refcount,
- session->id);
- session_put(session);
- }
- rcu_read_unlock();
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <common/common.h>
+#include <common/compat/path.h>
+#include <common/fd-tracker/utils.h>
+#include <common/time.h>
+#include <common/utils.h>
+#include <common/uuid.h>
+#include <urcu/rculist.h>
+
+#include <sys/stat.h>
+
+#include "ctf-trace.h"
+#include "lttng-relayd.h"
+#include "session.h"
+#include "sessiond-trace-chunks.h"
+#include "stream.h"
+#include <common/defaults.h>
+#include "utils.h"
+
+/* Global session id used in the session creation. */
+static uint64_t last_relay_session_id;
+static pthread_mutex_t last_relay_session_id_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int init_session_output_path_group_by_host(struct relay_session *session)
+{
+ /*
+ * session_directory:
+ *
+ * if base_path is \0'
+ * hostname/session_name
+ * else
+ * hostname/base_path
+ */
+ char *session_directory = NULL;
+ int ret = 0;
+
+ if (session->output_path[0] != '\0') {
+ goto end;
+ }
+ /*
+ * If base path is set, it overrides the session name for the
+ * session relative base path. No timestamp is appended if the
+ * base path is overridden.
+ *
+ * If the session name already contains the creation time (e.g.
+ * auto-<timestamp>, don't append yet another timestamp after
+ * the session name in the generated path.
+ *
+ * Otherwise, generate the path with session_name-<timestamp>.
+ */
+ if (session->base_path[0] != '\0') {
+ ret = asprintf(&session_directory, "%s/%s", session->hostname,
+ session->base_path);
+ } else if (session->session_name_contains_creation_time) {
+ ret = asprintf(&session_directory, "%s/%s", session->hostname,
+ session->session_name);
+ } else {
+ char session_creation_datetime[DATETIME_STR_LEN];
+
+ ret = time_to_datetime_str(
+ LTTNG_OPTIONAL_GET(session->creation_time),
+ session_creation_datetime,
+ sizeof(session_creation_datetime));
+ if (ret) {
+ ERR("Failed to format session creation timestamp while initializing session output directory handle");
+ ret = -1;
+ goto end;
+ }
+
+ ret = asprintf(&session_directory, "%s/%s-%s",
+ session->hostname, session->session_name,
+ session_creation_datetime);
+ }
+ if (ret < 0) {
+ PERROR("Failed to format session directory name");
+ goto end;
+ }
+
+ if (strlen(session_directory) >= LTTNG_PATH_MAX) {
+ ERR("Session output directory exceeds maximal length");
+ ret = -1;
+ goto end;
+ }
+ strcpy(session->output_path, session_directory);
+ ret = 0;
+
+end:
+ free(session_directory);
+ return ret;
+}
+
+static int init_session_output_path_group_by_session(
+ struct relay_session *session)
+{
+ /*
+ * session_directory:
+ *
+ * session_name/hostname-creation_time/base_path
+ *
+ * For session name including the datetime, use it as the complete name
+ * since. Do not perform modification on it since the datetime is an
+ * integral part of the name and how a user identify a session.
+ */
+ int ret = 0;
+ char *session_directory = NULL;
+ char creation_datetime[DATETIME_STR_LEN];
+
+ if (session->output_path[0] != '\0') {
+ /* output_path as been generated already */
+ goto end;
+ }
+
+ ret = time_to_datetime_str(LTTNG_OPTIONAL_GET(session->creation_time),
+ creation_datetime, sizeof(creation_datetime));
+ if (ret) {
+ ERR("Failed to format session creation timestamp while initializing session output directory handle");
+ ret = -1;
+ goto end;
+ }
+
+ ret = asprintf(&session_directory, "%s/%s-%s%s%s",
+ session->session_name, session->hostname,
+ creation_datetime,
+ session->base_path[0] != '\0' ? "/" : "",
+ session->base_path);
+ if (ret < 0) {
+ PERROR("Failed to format session directory name");
+ goto end;
+ }
+
+ if (strlen(session_directory) >= LTTNG_PATH_MAX) {
+ ERR("Session output directory exceeds maximal length");
+ ret = -1;
+ goto end;
+ }
+
+ strcpy(session->output_path, session_directory);
+ ret = 0;
+
+end:
+ free(session_directory);
+ return ret;
+}
+
+static int init_session_output_path(struct relay_session *session)
+{
+ int ret;
+
+ switch (opt_group_output_by) {
+ case RELAYD_GROUP_OUTPUT_BY_HOST:
+ ret = init_session_output_path_group_by_host(session);
+ break;
+ case RELAYD_GROUP_OUTPUT_BY_SESSION:
+ ret = init_session_output_path_group_by_session(session);
+ break;
+ case RELAYD_GROUP_OUTPUT_BY_UNKNOWN:
+ default:
+ abort();
+ break;
+ }
+
+ return ret;
+}
+
+static struct lttng_directory_handle *session_create_output_directory_handle(
+ struct relay_session *session)
+{
+ int ret;
+ /*
+ * relayd_output_path/session_directory
+ * e.g. /home/user/lttng-traces/hostname/session_name
+ */
+ char *full_session_path = NULL;
+ struct lttng_directory_handle *handle = NULL;
+
+ pthread_mutex_lock(&session->lock);
+ full_session_path = create_output_path(session->output_path);
+ if (!full_session_path) {
+ goto end;
+ }
+
+ ret = utils_mkdir_recursive(
+ full_session_path, S_IRWXU | S_IRWXG, -1, -1);
+ if (ret) {
+ ERR("Failed to create session output path \"%s\"",
+ full_session_path);
+ goto end;
+ }
+
+ handle = fd_tracker_create_directory_handle(the_fd_tracker, full_session_path);
+end:
+ pthread_mutex_unlock(&session->lock);
+ free(full_session_path);
+ return handle;
+}
+
+static int session_set_anonymous_chunk(struct relay_session *session)
+{
+ int ret = 0;
+ struct lttng_trace_chunk *chunk = NULL;
+ enum lttng_trace_chunk_status status;
+ struct lttng_directory_handle *output_directory;
+
+ output_directory = session_create_output_directory_handle(session);
+ if (!output_directory) {
+ goto end;
+ }
+
+ chunk = lttng_trace_chunk_create_anonymous();
+ if (!chunk) {
+ goto end;
+ }
+
+ lttng_trace_chunk_set_fd_tracker(chunk, the_fd_tracker);
+ status = lttng_trace_chunk_set_credentials_current_user(chunk);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+
+ status = lttng_trace_chunk_set_as_owner(chunk, output_directory);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+
+ session->current_trace_chunk = chunk;
+ chunk = NULL;
+end:
+ lttng_trace_chunk_put(chunk);
+ lttng_directory_handle_put(output_directory);
+ return ret;
+}
+
+/*
+ * Check if a name is safe to use in a path.
+ *
+ * A name that is deemed "path-safe":
+ * - Does not contains a path separator (/ or \, platform dependant),
+ * - Does not start with a '.' (hidden file/folder),
+ * - Is not empty.
+ */
+static bool is_name_path_safe(const char *name)
+{
+ const size_t name_len = strlen(name);
+
+ /* Not empty. */
+ if (name_len == 0) {
+ WARN("An empty name is not allowed to be used in a path");
+ return false;
+ }
+ /* Does not start with '.'. */
+ if (name[0] == '.') {
+ WARN("Name \"%s\" is not allowed to be used in a path since it starts with '.'", name);
+ return false;
+ }
+ /* Does not contain a path-separator. */
+ if (strchr(name, LTTNG_PATH_SEPARATOR)) {
+ WARN("Name \"%s\" is not allowed to be used in a path since it contains a path separator", name);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Create a new session by assigning a new session ID.
+ *
+ * Return allocated session or else NULL.
+ */
+struct relay_session *session_create(const char *session_name,
+ const char *hostname, const char *base_path,
+ uint32_t live_timer,
+ bool snapshot,
+ const lttng_uuid sessiond_uuid,
+ const uint64_t *id_sessiond,
+ const uint64_t *current_chunk_id,
+ const time_t *creation_time,
+ uint32_t major,
+ uint32_t minor,
+ bool session_name_contains_creation_time)
+{
+ int ret;
+ struct relay_session *session = NULL;
+
+ LTTNG_ASSERT(session_name);
+ LTTNG_ASSERT(hostname);
+ LTTNG_ASSERT(base_path);
+
+ if (!is_name_path_safe(session_name)) {
+ ERR("Refusing to create session as the provided session name is not path-safe");
+ goto error;
+ }
+ if (!is_name_path_safe(hostname)) {
+ ERR("Refusing to create session as the provided hostname is not path-safe");
+ goto error;
+ }
+ if (strstr(base_path, "../")) {
+ ERR("Invalid session base path walks up the path hierarchy: \"%s\"",
+ base_path);
+ goto error;
+ }
+
+ session = (relay_session *) zmalloc(sizeof(*session));
+ if (!session) {
+ PERROR("Failed to allocate session");
+ goto error;
+ }
+
+ pthread_mutex_lock(&last_relay_session_id_lock);
+ session->id = ++last_relay_session_id;
+ pthread_mutex_unlock(&last_relay_session_id_lock);
+
+ lttng_ht_node_init_u64(&session->session_n, session->id);
+ urcu_ref_init(&session->ref);
+ CDS_INIT_LIST_HEAD(&session->recv_list);
+ pthread_mutex_init(&session->lock, NULL);
+ pthread_mutex_init(&session->recv_list_lock, NULL);
+
+ if (lttng_strncpy(session->session_name, session_name,
+ sizeof(session->session_name))) {
+ WARN("Session name exceeds maximal allowed length");
+ goto error;
+ }
+ if (lttng_strncpy(session->hostname, hostname,
+ sizeof(session->hostname))) {
+ WARN("Hostname exceeds maximal allowed length");
+ goto error;
+ }
+ if (lttng_strncpy(session->base_path, base_path,
+ sizeof(session->base_path))) {
+ WARN("Base path exceeds maximal allowed length");
+ goto error;
+ }
+ if (creation_time) {
+ LTTNG_OPTIONAL_SET(&session->creation_time, *creation_time);
+ } else {
+ LTTNG_OPTIONAL_SET(&session->creation_time, time(NULL));
+ if (session->creation_time.value == (time_t) -1) {
+ PERROR("Failed to sample session creation time");
+ goto error;
+ }
+ }
+ session->session_name_contains_creation_time =
+ session_name_contains_creation_time;
+
+ session->ctf_traces_ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING);
+ if (!session->ctf_traces_ht) {
+ goto error;
+ }
+
+ session->major = major;
+ session->minor = minor;
+
+ session->live_timer = live_timer;
+ session->snapshot = snapshot;
+ lttng_uuid_copy(session->sessiond_uuid, sessiond_uuid);
+
+ if (id_sessiond) {
+ LTTNG_OPTIONAL_SET(&session->id_sessiond, *id_sessiond);
+ }
+
+ if (major == 2 && minor >= 11) {
+ /* Only applies for 2.11+ peers using trace chunks. */
+ ret = init_session_output_path(session);
+ if (ret) {
+ goto error;
+ }
+ }
+
+ ret = sessiond_trace_chunk_registry_session_created(
+ sessiond_trace_chunk_registry, sessiond_uuid);
+ if (ret) {
+ goto error;
+ }
+
+ if (id_sessiond && current_chunk_id) {
+ enum lttng_trace_chunk_status chunk_status;
+ struct lttng_directory_handle *session_output_directory;
+
+ session->current_trace_chunk =
+ sessiond_trace_chunk_registry_get_chunk(
+ sessiond_trace_chunk_registry,
+ session->sessiond_uuid,
+ session->id_sessiond.value,
+ *current_chunk_id);
+ if (!session->current_trace_chunk) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+ ERR("Could not find trace chunk: sessiond = {%s}, sessiond session id = %" PRIu64 ", trace chunk id = %" PRIu64,
+ uuid_str, *id_sessiond,
+ *current_chunk_id);
+ goto error;
+ }
+
+ chunk_status = lttng_trace_chunk_get_session_output_directory_handle(
+ session->current_trace_chunk,
+ &session_output_directory);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ goto error;
+ }
+
+ LTTNG_ASSERT(session_output_directory);
+ session->output_directory = session_output_directory;
+ } else if (!id_sessiond) {
+ /*
+ * Pre-2.11 peers will not announce trace chunks. An
+ * anonymous trace chunk which will remain set for the
+ * duration of the session is created.
+ */
+ ret = session_set_anonymous_chunk(session);
+ if (ret) {
+ goto error;
+ }
+ } else {
+ session->output_directory =
+ session_create_output_directory_handle(session);
+ if (!session->output_directory) {
+ goto error;
+ }
+ }
+
+ lttng_ht_add_unique_u64(sessions_ht, &session->session_n);
+ return session;
+
+error:
+ session_put(session);
+ return NULL;
+}
+
+/* Should be called with RCU read-side lock held. */
+bool session_get(struct relay_session *session)
+{
+ return urcu_ref_get_unless_zero(&session->ref);
+}
+
+/*
+ * Lookup a session within the session hash table using the session id
+ * as key. A session reference is taken when a session is returned.
+ * session_put() must be called on that session.
+ *
+ * Return session or NULL if not found.
+ */
+struct relay_session *session_get_by_id(uint64_t id)
+{
+ struct relay_session *session = NULL;
+ struct lttng_ht_node_u64 *node;
+ struct lttng_ht_iter iter;
+
+ rcu_read_lock();
+ lttng_ht_lookup(sessions_ht, &id, &iter);
+ node = lttng_ht_iter_get_node_u64(&iter);
+ if (!node) {
+ DBG("Session find by ID %" PRIu64 " id NOT found", id);
+ goto end;
+ }
+ session = caa_container_of(node, struct relay_session, session_n);
+ DBG("Session find by ID %" PRIu64 " id found", id);
+ if (!session_get(session)) {
+ session = NULL;
+ }
+end:
+ rcu_read_unlock();
+ return session;
+}
+
+static void rcu_destroy_session(struct rcu_head *rcu_head)
+{
+ struct relay_session *session =
+ caa_container_of(rcu_head, struct relay_session,
+ rcu_node);
+ /*
+ * Since each trace has a reference on the session, it means
+ * that if we are at the point where we teardown the session, no
+ * trace belonging to that session exist at this point.
+ * Calling lttng_ht_destroy in call_rcu worker thread so we
+ * don't hold the RCU read-side lock while calling it.
+ */
+ lttng_ht_destroy(session->ctf_traces_ht);
+ free(session);
+}
+
+/*
+ * Delete session from the given hash table.
+ *
+ * Return lttng ht del error code being 0 on success and 1 on failure.
+ */
+static int session_delete(struct relay_session *session)
+{
+ struct lttng_ht_iter iter;
+
+ iter.iter.node = &session->session_n.node;
+ return lttng_ht_del(sessions_ht, &iter);
+}
+
+
+static void destroy_session(struct relay_session *session)
+{
+ int ret;
+
+ ret = session_delete(session);
+ LTTNG_ASSERT(!ret);
+ lttng_trace_chunk_put(session->current_trace_chunk);
+ session->current_trace_chunk = NULL;
+ lttng_trace_chunk_put(session->pending_closure_trace_chunk);
+ session->pending_closure_trace_chunk = NULL;
+ ret = sessiond_trace_chunk_registry_session_destroyed(
+ sessiond_trace_chunk_registry, session->sessiond_uuid);
+ LTTNG_ASSERT(!ret);
+ lttng_directory_handle_put(session->output_directory);
+ session->output_directory = NULL;
+ call_rcu(&session->rcu_node, rcu_destroy_session);
+}
+
+static void session_release(struct urcu_ref *ref)
+{
+ struct relay_session *session =
+ caa_container_of(ref, struct relay_session, ref);
+
+ destroy_session(session);
+}
+
+void session_put(struct relay_session *session)
+{
+ if (!session) {
+ return;
+ }
+ rcu_read_lock();
+ urcu_ref_put(&session->ref, session_release);
+ rcu_read_unlock();
+}
+
+int session_close(struct relay_session *session)
+{
+ int ret = 0;
+ struct ctf_trace *trace;
+ struct lttng_ht_iter iter;
+ struct relay_stream *stream;
+
+ pthread_mutex_lock(&session->lock);
+ DBG("closing session %" PRIu64 ": is conn already closed %d",
+ session->id, session->connection_closed);
+ session->connection_closed = true;
+ pthread_mutex_unlock(&session->lock);
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(session->ctf_traces_ht->ht,
+ &iter.iter, trace, node.node) {
+ ret = ctf_trace_close(trace);
+ if (ret) {
+ goto rcu_unlock;
+ }
+ }
+ cds_list_for_each_entry_rcu(stream, &session->recv_list,
+ recv_node) {
+ /* Close streams which have not been published yet. */
+ try_stream_close(stream);
+ }
+rcu_unlock:
+ rcu_read_unlock();
+ if (ret) {
+ return ret;
+ }
+ /* Put self-reference from create. */
+ session_put(session);
+ return ret;
+}
+
+int session_abort(struct relay_session *session)
+{
+ int ret = 0;
+
+ if (!session) {
+ return 0;
+ }
+
+ pthread_mutex_lock(&session->lock);
+ DBG("aborting session %" PRIu64, session->id);
+ session->aborted = true;
+ pthread_mutex_unlock(&session->lock);
+ return ret;
+}
+
+void print_sessions(void)
+{
+ struct lttng_ht_iter iter;
+ struct relay_session *session;
+
+ if (!sessions_ht) {
+ return;
+ }
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(sessions_ht->ht, &iter.iter, session,
+ session_n.node) {
+ if (!session_get(session)) {
+ continue;
+ }
+ DBG("session %p refcount %ld session %" PRIu64,
+ session,
+ session->ref.refcount,
+ session->id);
+ session_put(session);
+ }
+ rcu_read_unlock();
+}
+++ /dev/null
-/*
- * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#include "sessiond-trace-chunks.h"
-#include <urcu.h>
-#include <urcu/rculfhash.h>
-#include <urcu/ref.h>
-#include <common/macros.h>
-#include <common/hashtable/hashtable.h>
-#include <common/hashtable/utils.h>
-#include <common/trace-chunk-registry.h>
-#include <common/defaults.h>
-#include <common/error.h>
-#include <common/string-utils/format.h>
-#include <stdio.h>
-#include <inttypes.h>
-
-/*
- * Lifetime of trace chunks within the relay daemon.
- *
- * Trace chunks are shared accross connections initiated from a given
- * session daemon. When a session is created by a consumer daemon, the
- * UUID of its associated session daemon is transmitted (in the case of
- * 2.11+ consumer daemons).
- *
- * The sessiond_trace_chunk_registry_new_session() and
- * sessiond_trace_chunk_registry_session_closed() methods create and
- * manage the reference count of lttng_trace_chunk_registry objects
- * associated to the various sessiond instances served by the relay daemon.
- *
- * When all sessions associated with a given sessiond instance are
- * destroyed, its registry is destroyed.
- *
- * lttng_trace_chunk objects are uniquely identified by the
- * (sessiond_uuid, sessiond_session_id, chunk_id) tuple. If a trace chunk
- * matching that tuple already exists, a new reference to the trace chunk
- * is acquired and it is returned to the caller. Otherwise, a new trace
- * chunk is created. This is how trace chunks are de-duplicated across
- * multiple consumer daemons managed by the same session daemon.
- *
- * Note that trace chunks are always added to their matching
- * lttng_trace_chunk_registry. They are automatically removed from the
- * trace chunk registry when their reference count reaches zero.
- */
-
-/*
- * It is assumed that the sessiond_trace_chunk_registry is created and
- * destroyed by the same thread.
- */
-struct sessiond_trace_chunk_registry {
- /* Maps an lttng_uuid to an lttng_trace_chunk_registry. */
- struct cds_lfht *ht;
-};
-
-struct trace_chunk_registry_ht_key {
- lttng_uuid sessiond_uuid;
-};
-
-struct trace_chunk_registry_ht_element {
- struct trace_chunk_registry_ht_key key;
- struct urcu_ref ref;
- /* Node into the sessiond_trace_chunk_registry's hash table. */
- struct cds_lfht_node ht_node;
- /* Used for defered call_rcu reclaim. */
- struct rcu_head rcu_node;
- struct lttng_trace_chunk_registry *trace_chunk_registry;
- struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry;
-};
-
-static
-unsigned long trace_chunk_registry_ht_key_hash(
- const struct trace_chunk_registry_ht_key *key)
-{
- uint64_t uuid_h1 = ((uint64_t *) key->sessiond_uuid)[0];
- uint64_t uuid_h2 = ((uint64_t *) key->sessiond_uuid)[1];
-
- return hash_key_u64(&uuid_h1, lttng_ht_seed) ^
- hash_key_u64(&uuid_h2, lttng_ht_seed);
-}
-
-/* cds_lfht match function */
-static
-int trace_chunk_registry_ht_key_match(struct cds_lfht_node *node,
- const void *_key)
-{
- const struct trace_chunk_registry_ht_key *key =
- (struct trace_chunk_registry_ht_key *) _key;
- struct trace_chunk_registry_ht_element *registry;
-
- registry = container_of(node, typeof(*registry), ht_node);
- return lttng_uuid_is_equal(key->sessiond_uuid,
- registry->key.sessiond_uuid);
-}
-
-static
-void trace_chunk_registry_ht_element_free(struct rcu_head *node)
-{
- struct trace_chunk_registry_ht_element *element =
- container_of(node, typeof(*element), rcu_node);
-
- free(element);
-}
-
-static
-void trace_chunk_registry_ht_element_release(struct urcu_ref *ref)
-{
- struct trace_chunk_registry_ht_element *element =
- container_of(ref, typeof(*element), ref);
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(element->key.sessiond_uuid, uuid_str);
-
- DBG("Destroying trace chunk registry associated to sessiond {%s}",
- uuid_str);
- if (element->sessiond_trace_chunk_registry) {
- /* Unpublish. */
- rcu_read_lock();
- cds_lfht_del(element->sessiond_trace_chunk_registry->ht,
- &element->ht_node);
- rcu_read_unlock();
- element->sessiond_trace_chunk_registry = NULL;
- }
-
- lttng_trace_chunk_registry_destroy(element->trace_chunk_registry);
- /* Defered reclaim of the object */
- call_rcu(&element->rcu_node, trace_chunk_registry_ht_element_free);
-}
-
-static
-bool trace_chunk_registry_ht_element_get(
- struct trace_chunk_registry_ht_element *element)
-{
- return urcu_ref_get_unless_zero(&element->ref);
-}
-
-static
-void trace_chunk_registry_ht_element_put(
- struct trace_chunk_registry_ht_element *element)
-{
- if (!element) {
- return;
- }
-
- urcu_ref_put(&element->ref, trace_chunk_registry_ht_element_release);
-}
-
-/* Acquires a reference to the returned element on behalf of the caller. */
-static
-struct trace_chunk_registry_ht_element *trace_chunk_registry_ht_element_find(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const struct trace_chunk_registry_ht_key *key)
-{
- struct trace_chunk_registry_ht_element *element = NULL;
- struct cds_lfht_node *node;
- struct cds_lfht_iter iter;
-
- rcu_read_lock();
- cds_lfht_lookup(sessiond_registry->ht,
- trace_chunk_registry_ht_key_hash(key),
- trace_chunk_registry_ht_key_match,
- key,
- &iter);
- node = cds_lfht_iter_get_node(&iter);
- if (node) {
- element = container_of(node, typeof(*element), ht_node);
- /*
- * Only consider the look-up as successful if a reference
- * could be acquired.
- */
- if (!trace_chunk_registry_ht_element_get(element)) {
- element = NULL;
- }
- }
- rcu_read_unlock();
- return element;
-}
-
-static
-int trace_chunk_registry_ht_element_create(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const struct trace_chunk_registry_ht_key *key)
-{
- int ret = 0;
- struct trace_chunk_registry_ht_element *new_element;
- struct lttng_trace_chunk_registry *trace_chunk_registry;
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(key->sessiond_uuid, uuid_str);
-
- trace_chunk_registry = lttng_trace_chunk_registry_create();
- if (!trace_chunk_registry) {
- ret = -1;
- goto end;
- }
-
- new_element = zmalloc(sizeof(*new_element));
- if (!new_element) {
- ret = -1;
- goto end;
- }
-
- memcpy(&new_element->key, key, sizeof(new_element->key));
- urcu_ref_init(&new_element->ref);
- cds_lfht_node_init(&new_element->ht_node);
- new_element->trace_chunk_registry = trace_chunk_registry;
- trace_chunk_registry = NULL;
-
- /* Attempt to publish the new element. */
- rcu_read_lock();
- while (1) {
- struct cds_lfht_node *published_node;
- struct trace_chunk_registry_ht_element *published_element;
-
- published_node = cds_lfht_add_unique(sessiond_registry->ht,
- trace_chunk_registry_ht_key_hash(&new_element->key),
- trace_chunk_registry_ht_key_match,
- &new_element->key,
- &new_element->ht_node);
- if (published_node == &new_element->ht_node) {
- /* New element published successfully. */
- DBG("Created trace chunk registry for sessiond {%s}",
- uuid_str);
- new_element->sessiond_trace_chunk_registry =
- sessiond_registry;
- break;
- }
-
- /*
- * An equivalent element was published during the creation of
- * this element. Attempt to acquire a reference to the one that
- * was already published and release the reference to the copy
- * we created if successful.
- */
- published_element = container_of(published_node,
- typeof(*published_element), ht_node);
- if (trace_chunk_registry_ht_element_get(published_element)) {
- DBG("Acquired reference to trace chunk registry of sessiond {%s}",
- uuid_str);
- trace_chunk_registry_ht_element_put(new_element);
- new_element = NULL;
- break;
- }
- /*
- * A reference to the previously published element could not
- * be acquired. Hence, retry to publish our copy of the
- * element.
- */
- }
- rcu_read_unlock();
-end:
- if (ret < 0) {
- ERR("Failed to create trace chunk registry for session daemon {%s}",
- uuid_str);
- }
- lttng_trace_chunk_registry_destroy(trace_chunk_registry);
- return ret;
-}
-
-struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry_create(void)
-{
- struct sessiond_trace_chunk_registry *sessiond_registry =
- zmalloc(sizeof(*sessiond_registry));
-
- if (!sessiond_registry) {
- goto end;
- }
-
- sessiond_registry->ht = cds_lfht_new(DEFAULT_HT_SIZE,
- 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
- if (!sessiond_registry->ht) {
- goto error;
- }
-
-end:
- return sessiond_registry;
-error:
- sessiond_trace_chunk_registry_destroy(sessiond_registry);
- return NULL;
-}
-
-void sessiond_trace_chunk_registry_destroy(
- struct sessiond_trace_chunk_registry *sessiond_registry)
-{
- int ret = cds_lfht_destroy(sessiond_registry->ht, NULL);
-
- LTTNG_ASSERT(!ret);
- free(sessiond_registry);
-}
-
-int sessiond_trace_chunk_registry_session_created(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid)
-{
- int ret = 0;
- struct trace_chunk_registry_ht_key key;
- struct trace_chunk_registry_ht_element *element;
-
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
-
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (element) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
- DBG("Acquired reference to trace chunk registry of sessiond {%s}",
- uuid_str);
- goto end;
- } else {
- ret = trace_chunk_registry_ht_element_create(
- sessiond_registry, &key);
- }
-end:
- return ret;
-}
-
-int sessiond_trace_chunk_registry_session_destroyed(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid)
-{
- int ret = 0;
- struct trace_chunk_registry_ht_key key;
- struct trace_chunk_registry_ht_element *element;
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
-
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (element) {
- DBG("Releasing reference to trace chunk registry of sessiond {%s}",
- uuid_str);
- /*
- * Release the reference held by the session and the reference
- * acquired through the "find" operation.
- */
- trace_chunk_registry_ht_element_put(element);
- trace_chunk_registry_ht_element_put(element);
- } else {
- ERR("Failed to find trace chunk registry of sessiond {%s}",
- uuid_str);
- ret = -1;
- }
- return ret;
-}
-
-struct lttng_trace_chunk *sessiond_trace_chunk_registry_publish_chunk(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid, uint64_t session_id,
- struct lttng_trace_chunk *new_chunk)
-{
- enum lttng_trace_chunk_status status;
- uint64_t chunk_id;
- bool is_anonymous_chunk;
- struct trace_chunk_registry_ht_key key;
- struct trace_chunk_registry_ht_element *element = NULL;
- char uuid_str[LTTNG_UUID_STR_LEN];
- char chunk_id_str[MAX_INT_DEC_LEN(typeof(chunk_id))] = "-1";
- struct lttng_trace_chunk *published_chunk = NULL;
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
-
- status = lttng_trace_chunk_get_id(new_chunk, &chunk_id);
- if (status == LTTNG_TRACE_CHUNK_STATUS_OK) {
- int ret;
-
- ret = snprintf(chunk_id_str, sizeof(chunk_id_str), "%" PRIu64,
- chunk_id);
- if (ret < 0) {
- lttng_strncpy(chunk_id_str, "-1", sizeof(chunk_id_str));
- WARN("Failed to format trace chunk id");
- }
- is_anonymous_chunk = false;
- } else if (status == LTTNG_TRACE_CHUNK_STATUS_NONE) {
- is_anonymous_chunk = true;
- } else {
- ERR("Failed to get trace chunk id");
- goto end;
- }
-
- DBG("Attempting to publish trace chunk: sessiond {%s}, session_id = "
- "%" PRIu64 ", chunk_id = %s",
- uuid_str, session_id,
- is_anonymous_chunk ? "anonymous" : chunk_id_str);
-
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (!element) {
- ERR("Failed to find registry of sessiond {%s}", uuid_str);
- goto end;
- }
-
- published_chunk = lttng_trace_chunk_registry_publish_chunk(
- element->trace_chunk_registry, session_id, new_chunk);
- /*
- * At this point, two references to the published chunks exist. One
- * is taken by the registry while the other is being returned to the
- * caller. In the use case of the relay daemon, the reference held
- * by the registry itself is undesirable.
- *
- * We want the trace chunk to be removed from the registry as soon
- * as it is not being used by the relay daemon (through a session
- * or a stream). This differs from the behaviour of the consumer
- * daemon which relies on an explicit command from the session
- * daemon to release the registry's reference.
- */
- lttng_trace_chunk_put(published_chunk);
-end:
- trace_chunk_registry_ht_element_put(element);
- return published_chunk;
-}
-
-struct lttng_trace_chunk *
-sessiond_trace_chunk_registry_get_anonymous_chunk(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid,
- uint64_t session_id)
-{
- struct lttng_trace_chunk *chunk = NULL;
- struct trace_chunk_registry_ht_element *element;
- struct trace_chunk_registry_ht_key key;
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
-
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (!element) {
- ERR("Failed to find trace chunk registry of sessiond {%s}",
- uuid_str);
- goto end;
- }
-
- chunk = lttng_trace_chunk_registry_find_anonymous_chunk(
- element->trace_chunk_registry,
- session_id);
- trace_chunk_registry_ht_element_put(element);
-end:
- return chunk;
-}
-
-struct lttng_trace_chunk *
-sessiond_trace_chunk_registry_get_chunk(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid,
- uint64_t session_id, uint64_t chunk_id)
-{
- struct lttng_trace_chunk *chunk = NULL;
- struct trace_chunk_registry_ht_element *element;
- struct trace_chunk_registry_ht_key key;
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
-
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (!element) {
- ERR("Failed to find trace chunk registry of sessiond {%s}",
- uuid_str);
- goto end;
- }
-
- chunk = lttng_trace_chunk_registry_find_chunk(
- element->trace_chunk_registry,
- session_id, chunk_id);
- trace_chunk_registry_ht_element_put(element);
-end:
- return chunk;
-}
-
-int sessiond_trace_chunk_registry_chunk_exists(
- struct sessiond_trace_chunk_registry *sessiond_registry,
- const lttng_uuid sessiond_uuid,
- uint64_t session_id, uint64_t chunk_id, bool *chunk_exists)
-{
- int ret;
- struct trace_chunk_registry_ht_element *element;
- struct trace_chunk_registry_ht_key key;
-
- lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
- element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
- if (!element) {
- char uuid_str[LTTNG_UUID_STR_LEN];
-
- lttng_uuid_to_str(sessiond_uuid, uuid_str);
- /*
- * While this certainly means that the chunk does not exist,
- * it is unexpected for a chunk existence query to target a
- * session daemon that does not have an active
- * connection/registry. This would indicate a protocol
- * (or internal) error.
- */
- ERR("Failed to find trace chunk registry of sessiond {%s}",
- uuid_str);
- ret = -1;
- goto end;
- }
-
- ret = lttng_trace_chunk_registry_chunk_exists(
- element->trace_chunk_registry,
- session_id, chunk_id, chunk_exists);
- trace_chunk_registry_ht_element_put(element);
-end:
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include "sessiond-trace-chunks.h"
+#include <urcu.h>
+#include <urcu/rculfhash.h>
+#include <urcu/ref.h>
+#include <common/macros.h>
+#include <common/hashtable/hashtable.h>
+#include <common/hashtable/utils.h>
+#include <common/trace-chunk-registry.h>
+#include <common/defaults.h>
+#include <common/error.h>
+#include <common/string-utils/format.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+/*
+ * Lifetime of trace chunks within the relay daemon.
+ *
+ * Trace chunks are shared accross connections initiated from a given
+ * session daemon. When a session is created by a consumer daemon, the
+ * UUID of its associated session daemon is transmitted (in the case of
+ * 2.11+ consumer daemons).
+ *
+ * The sessiond_trace_chunk_registry_new_session() and
+ * sessiond_trace_chunk_registry_session_closed() methods create and
+ * manage the reference count of lttng_trace_chunk_registry objects
+ * associated to the various sessiond instances served by the relay daemon.
+ *
+ * When all sessions associated with a given sessiond instance are
+ * destroyed, its registry is destroyed.
+ *
+ * lttng_trace_chunk objects are uniquely identified by the
+ * (sessiond_uuid, sessiond_session_id, chunk_id) tuple. If a trace chunk
+ * matching that tuple already exists, a new reference to the trace chunk
+ * is acquired and it is returned to the caller. Otherwise, a new trace
+ * chunk is created. This is how trace chunks are de-duplicated across
+ * multiple consumer daemons managed by the same session daemon.
+ *
+ * Note that trace chunks are always added to their matching
+ * lttng_trace_chunk_registry. They are automatically removed from the
+ * trace chunk registry when their reference count reaches zero.
+ */
+
+/*
+ * It is assumed that the sessiond_trace_chunk_registry is created and
+ * destroyed by the same thread.
+ */
+struct sessiond_trace_chunk_registry {
+ /* Maps an lttng_uuid to an lttng_trace_chunk_registry. */
+ struct cds_lfht *ht;
+};
+
+struct trace_chunk_registry_ht_key {
+ lttng_uuid sessiond_uuid;
+};
+
+struct trace_chunk_registry_ht_element {
+ struct trace_chunk_registry_ht_key key;
+ struct urcu_ref ref;
+ /* Node into the sessiond_trace_chunk_registry's hash table. */
+ struct cds_lfht_node ht_node;
+ /* Used for defered call_rcu reclaim. */
+ struct rcu_head rcu_node;
+ struct lttng_trace_chunk_registry *trace_chunk_registry;
+ struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry;
+};
+
+static
+unsigned long trace_chunk_registry_ht_key_hash(
+ const struct trace_chunk_registry_ht_key *key)
+{
+ uint64_t uuid_h1 = ((uint64_t *) key->sessiond_uuid)[0];
+ uint64_t uuid_h2 = ((uint64_t *) key->sessiond_uuid)[1];
+
+ return hash_key_u64(&uuid_h1, lttng_ht_seed) ^
+ hash_key_u64(&uuid_h2, lttng_ht_seed);
+}
+
+/* cds_lfht match function */
+static
+int trace_chunk_registry_ht_key_match(struct cds_lfht_node *node,
+ const void *_key)
+{
+ const struct trace_chunk_registry_ht_key *key =
+ (struct trace_chunk_registry_ht_key *) _key;
+ struct trace_chunk_registry_ht_element *registry;
+
+ registry = container_of(node, typeof(*registry), ht_node);
+ return lttng_uuid_is_equal(key->sessiond_uuid,
+ registry->key.sessiond_uuid);
+}
+
+static
+void trace_chunk_registry_ht_element_free(struct rcu_head *node)
+{
+ struct trace_chunk_registry_ht_element *element =
+ container_of(node, typeof(*element), rcu_node);
+
+ free(element);
+}
+
+static
+void trace_chunk_registry_ht_element_release(struct urcu_ref *ref)
+{
+ struct trace_chunk_registry_ht_element *element =
+ container_of(ref, typeof(*element), ref);
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(element->key.sessiond_uuid, uuid_str);
+
+ DBG("Destroying trace chunk registry associated to sessiond {%s}",
+ uuid_str);
+ if (element->sessiond_trace_chunk_registry) {
+ /* Unpublish. */
+ rcu_read_lock();
+ cds_lfht_del(element->sessiond_trace_chunk_registry->ht,
+ &element->ht_node);
+ rcu_read_unlock();
+ element->sessiond_trace_chunk_registry = NULL;
+ }
+
+ lttng_trace_chunk_registry_destroy(element->trace_chunk_registry);
+ /* Defered reclaim of the object */
+ call_rcu(&element->rcu_node, trace_chunk_registry_ht_element_free);
+}
+
+static
+bool trace_chunk_registry_ht_element_get(
+ struct trace_chunk_registry_ht_element *element)
+{
+ return urcu_ref_get_unless_zero(&element->ref);
+}
+
+static
+void trace_chunk_registry_ht_element_put(
+ struct trace_chunk_registry_ht_element *element)
+{
+ if (!element) {
+ return;
+ }
+
+ urcu_ref_put(&element->ref, trace_chunk_registry_ht_element_release);
+}
+
+/* Acquires a reference to the returned element on behalf of the caller. */
+static
+struct trace_chunk_registry_ht_element *trace_chunk_registry_ht_element_find(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const struct trace_chunk_registry_ht_key *key)
+{
+ struct trace_chunk_registry_ht_element *element = NULL;
+ struct cds_lfht_node *node;
+ struct cds_lfht_iter iter;
+
+ rcu_read_lock();
+ cds_lfht_lookup(sessiond_registry->ht,
+ trace_chunk_registry_ht_key_hash(key),
+ trace_chunk_registry_ht_key_match,
+ key,
+ &iter);
+ node = cds_lfht_iter_get_node(&iter);
+ if (node) {
+ element = container_of(node, typeof(*element), ht_node);
+ /*
+ * Only consider the look-up as successful if a reference
+ * could be acquired.
+ */
+ if (!trace_chunk_registry_ht_element_get(element)) {
+ element = NULL;
+ }
+ }
+ rcu_read_unlock();
+ return element;
+}
+
+static
+int trace_chunk_registry_ht_element_create(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const struct trace_chunk_registry_ht_key *key)
+{
+ int ret = 0;
+ struct trace_chunk_registry_ht_element *new_element;
+ struct lttng_trace_chunk_registry *trace_chunk_registry;
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(key->sessiond_uuid, uuid_str);
+
+ trace_chunk_registry = lttng_trace_chunk_registry_create();
+ if (!trace_chunk_registry) {
+ ret = -1;
+ goto end;
+ }
+
+ new_element = (trace_chunk_registry_ht_element *) zmalloc(sizeof(*new_element));
+ if (!new_element) {
+ ret = -1;
+ goto end;
+ }
+
+ memcpy(&new_element->key, key, sizeof(new_element->key));
+ urcu_ref_init(&new_element->ref);
+ cds_lfht_node_init(&new_element->ht_node);
+ new_element->trace_chunk_registry = trace_chunk_registry;
+ trace_chunk_registry = NULL;
+
+ /* Attempt to publish the new element. */
+ rcu_read_lock();
+ while (1) {
+ struct cds_lfht_node *published_node;
+ struct trace_chunk_registry_ht_element *published_element;
+
+ published_node = cds_lfht_add_unique(sessiond_registry->ht,
+ trace_chunk_registry_ht_key_hash(&new_element->key),
+ trace_chunk_registry_ht_key_match,
+ &new_element->key,
+ &new_element->ht_node);
+ if (published_node == &new_element->ht_node) {
+ /* New element published successfully. */
+ DBG("Created trace chunk registry for sessiond {%s}",
+ uuid_str);
+ new_element->sessiond_trace_chunk_registry =
+ sessiond_registry;
+ break;
+ }
+
+ /*
+ * An equivalent element was published during the creation of
+ * this element. Attempt to acquire a reference to the one that
+ * was already published and release the reference to the copy
+ * we created if successful.
+ */
+ published_element = container_of(published_node,
+ typeof(*published_element), ht_node);
+ if (trace_chunk_registry_ht_element_get(published_element)) {
+ DBG("Acquired reference to trace chunk registry of sessiond {%s}",
+ uuid_str);
+ trace_chunk_registry_ht_element_put(new_element);
+ new_element = NULL;
+ break;
+ }
+ /*
+ * A reference to the previously published element could not
+ * be acquired. Hence, retry to publish our copy of the
+ * element.
+ */
+ }
+ rcu_read_unlock();
+end:
+ if (ret < 0) {
+ ERR("Failed to create trace chunk registry for session daemon {%s}",
+ uuid_str);
+ }
+ lttng_trace_chunk_registry_destroy(trace_chunk_registry);
+ return ret;
+}
+
+struct sessiond_trace_chunk_registry *sessiond_trace_chunk_registry_create(void)
+{
+ struct sessiond_trace_chunk_registry *sessiond_registry =
+ (sessiond_trace_chunk_registry *) zmalloc(sizeof(*sessiond_registry));
+
+ if (!sessiond_registry) {
+ goto end;
+ }
+
+ sessiond_registry->ht = cds_lfht_new(DEFAULT_HT_SIZE,
+ 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
+ if (!sessiond_registry->ht) {
+ goto error;
+ }
+
+end:
+ return sessiond_registry;
+error:
+ sessiond_trace_chunk_registry_destroy(sessiond_registry);
+ return NULL;
+}
+
+void sessiond_trace_chunk_registry_destroy(
+ struct sessiond_trace_chunk_registry *sessiond_registry)
+{
+ int ret = cds_lfht_destroy(sessiond_registry->ht, NULL);
+
+ LTTNG_ASSERT(!ret);
+ free(sessiond_registry);
+}
+
+int sessiond_trace_chunk_registry_session_created(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid)
+{
+ int ret = 0;
+ struct trace_chunk_registry_ht_key key;
+ struct trace_chunk_registry_ht_element *element;
+
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (element) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+ DBG("Acquired reference to trace chunk registry of sessiond {%s}",
+ uuid_str);
+ goto end;
+ } else {
+ ret = trace_chunk_registry_ht_element_create(
+ sessiond_registry, &key);
+ }
+end:
+ return ret;
+}
+
+int sessiond_trace_chunk_registry_session_destroyed(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid)
+{
+ int ret = 0;
+ struct trace_chunk_registry_ht_key key;
+ struct trace_chunk_registry_ht_element *element;
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (element) {
+ DBG("Releasing reference to trace chunk registry of sessiond {%s}",
+ uuid_str);
+ /*
+ * Release the reference held by the session and the reference
+ * acquired through the "find" operation.
+ */
+ trace_chunk_registry_ht_element_put(element);
+ trace_chunk_registry_ht_element_put(element);
+ } else {
+ ERR("Failed to find trace chunk registry of sessiond {%s}",
+ uuid_str);
+ ret = -1;
+ }
+ return ret;
+}
+
+struct lttng_trace_chunk *sessiond_trace_chunk_registry_publish_chunk(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid, uint64_t session_id,
+ struct lttng_trace_chunk *new_chunk)
+{
+ enum lttng_trace_chunk_status status;
+ uint64_t chunk_id;
+ bool is_anonymous_chunk;
+ struct trace_chunk_registry_ht_key key;
+ struct trace_chunk_registry_ht_element *element = NULL;
+ char uuid_str[LTTNG_UUID_STR_LEN];
+ char chunk_id_str[MAX_INT_DEC_LEN(typeof(chunk_id))] = "-1";
+ struct lttng_trace_chunk *published_chunk = NULL;
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+
+ status = lttng_trace_chunk_get_id(new_chunk, &chunk_id);
+ if (status == LTTNG_TRACE_CHUNK_STATUS_OK) {
+ int ret;
+
+ ret = snprintf(chunk_id_str, sizeof(chunk_id_str), "%" PRIu64,
+ chunk_id);
+ if (ret < 0) {
+ lttng_strncpy(chunk_id_str, "-1", sizeof(chunk_id_str));
+ WARN("Failed to format trace chunk id");
+ }
+ is_anonymous_chunk = false;
+ } else if (status == LTTNG_TRACE_CHUNK_STATUS_NONE) {
+ is_anonymous_chunk = true;
+ } else {
+ ERR("Failed to get trace chunk id");
+ goto end;
+ }
+
+ DBG("Attempting to publish trace chunk: sessiond {%s}, session_id = "
+ "%" PRIu64 ", chunk_id = %s",
+ uuid_str, session_id,
+ is_anonymous_chunk ? "anonymous" : chunk_id_str);
+
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (!element) {
+ ERR("Failed to find registry of sessiond {%s}", uuid_str);
+ goto end;
+ }
+
+ published_chunk = lttng_trace_chunk_registry_publish_chunk(
+ element->trace_chunk_registry, session_id, new_chunk);
+ /*
+ * At this point, two references to the published chunks exist. One
+ * is taken by the registry while the other is being returned to the
+ * caller. In the use case of the relay daemon, the reference held
+ * by the registry itself is undesirable.
+ *
+ * We want the trace chunk to be removed from the registry as soon
+ * as it is not being used by the relay daemon (through a session
+ * or a stream). This differs from the behaviour of the consumer
+ * daemon which relies on an explicit command from the session
+ * daemon to release the registry's reference.
+ */
+ lttng_trace_chunk_put(published_chunk);
+end:
+ trace_chunk_registry_ht_element_put(element);
+ return published_chunk;
+}
+
+struct lttng_trace_chunk *
+sessiond_trace_chunk_registry_get_anonymous_chunk(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid,
+ uint64_t session_id)
+{
+ struct lttng_trace_chunk *chunk = NULL;
+ struct trace_chunk_registry_ht_element *element;
+ struct trace_chunk_registry_ht_key key;
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (!element) {
+ ERR("Failed to find trace chunk registry of sessiond {%s}",
+ uuid_str);
+ goto end;
+ }
+
+ chunk = lttng_trace_chunk_registry_find_anonymous_chunk(
+ element->trace_chunk_registry,
+ session_id);
+ trace_chunk_registry_ht_element_put(element);
+end:
+ return chunk;
+}
+
+struct lttng_trace_chunk *
+sessiond_trace_chunk_registry_get_chunk(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid,
+ uint64_t session_id, uint64_t chunk_id)
+{
+ struct lttng_trace_chunk *chunk = NULL;
+ struct trace_chunk_registry_ht_element *element;
+ struct trace_chunk_registry_ht_key key;
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (!element) {
+ ERR("Failed to find trace chunk registry of sessiond {%s}",
+ uuid_str);
+ goto end;
+ }
+
+ chunk = lttng_trace_chunk_registry_find_chunk(
+ element->trace_chunk_registry,
+ session_id, chunk_id);
+ trace_chunk_registry_ht_element_put(element);
+end:
+ return chunk;
+}
+
+int sessiond_trace_chunk_registry_chunk_exists(
+ struct sessiond_trace_chunk_registry *sessiond_registry,
+ const lttng_uuid sessiond_uuid,
+ uint64_t session_id, uint64_t chunk_id, bool *chunk_exists)
+{
+ int ret;
+ struct trace_chunk_registry_ht_element *element;
+ struct trace_chunk_registry_ht_key key;
+
+ lttng_uuid_copy(key.sessiond_uuid, sessiond_uuid);
+ element = trace_chunk_registry_ht_element_find(sessiond_registry, &key);
+ if (!element) {
+ char uuid_str[LTTNG_UUID_STR_LEN];
+
+ lttng_uuid_to_str(sessiond_uuid, uuid_str);
+ /*
+ * While this certainly means that the chunk does not exist,
+ * it is unexpected for a chunk existence query to target a
+ * session daemon that does not have an active
+ * connection/registry. This would indicate a protocol
+ * (or internal) error.
+ */
+ ERR("Failed to find trace chunk registry of sessiond {%s}",
+ uuid_str);
+ ret = -1;
+ goto end;
+ }
+
+ ret = lttng_trace_chunk_registry_chunk_exists(
+ element->trace_chunk_registry,
+ session_id, chunk_id, chunk_exists);
+ trace_chunk_registry_ht_element_put(element);
+end:
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <common/defaults.h>
-#include <common/fs-handle.h>
-#include <common/sessiond-comm/relayd.h>
-#include <common/utils.h>
-#include <sys/stat.h>
-#include <urcu/rculist.h>
-
-#include "lttng-relayd.h"
-#include "index.h"
-#include "stream.h"
-#include "viewer-stream.h"
-
-#include <sys/types.h>
-#include <fcntl.h>
-
-#define FILE_IO_STACK_BUFFER_SIZE 65536
-
-/* Should be called with RCU read-side lock held. */
-bool stream_get(struct relay_stream *stream)
-{
- return urcu_ref_get_unless_zero(&stream->ref);
-}
-
-/*
- * Get stream from stream id from the streams hash table. Return stream
- * if found else NULL. A stream reference is taken when a stream is
- * returned. stream_put() must be called on that stream.
- */
-struct relay_stream *stream_get_by_id(uint64_t stream_id)
-{
- struct lttng_ht_node_u64 *node;
- struct lttng_ht_iter iter;
- struct relay_stream *stream = NULL;
-
- rcu_read_lock();
- lttng_ht_lookup(relay_streams_ht, &stream_id, &iter);
- node = lttng_ht_iter_get_node_u64(&iter);
- if (!node) {
- DBG("Relay stream %" PRIu64 " not found", stream_id);
- goto end;
- }
- stream = caa_container_of(node, struct relay_stream, node);
- if (!stream_get(stream)) {
- stream = NULL;
- }
-end:
- rcu_read_unlock();
- return stream;
-}
-
-static void stream_complete_rotation(struct relay_stream *stream)
-{
- DBG("Rotation completed for stream %" PRIu64, stream->stream_handle);
- if (stream->ongoing_rotation.value.next_trace_chunk) {
- tracefile_array_reset(stream->tfa);
- tracefile_array_commit_seq(stream->tfa,
- stream->index_received_seqcount);
- }
- lttng_trace_chunk_put(stream->trace_chunk);
- stream->trace_chunk = stream->ongoing_rotation.value.next_trace_chunk;
- stream->ongoing_rotation = (typeof(stream->ongoing_rotation)) {};
- stream->completed_rotation_count++;
-}
-
-static int stream_create_data_output_file_from_trace_chunk(
- struct relay_stream *stream,
- struct lttng_trace_chunk *trace_chunk,
- bool force_unlink,
- struct fs_handle **out_file)
-{
- int ret;
- char stream_path[LTTNG_PATH_MAX];
- enum lttng_trace_chunk_status status;
- const int flags = O_RDWR | O_CREAT | O_TRUNC;
- const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
-
- ASSERT_LOCKED(stream->lock);
-
- ret = utils_stream_file_path(stream->path_name, stream->channel_name,
- stream->tracefile_size, stream->tracefile_current_index,
- NULL, stream_path, sizeof(stream_path));
- if (ret < 0) {
- goto end;
- }
-
- if (stream->tracefile_wrapped_around || force_unlink) {
- /*
- * The on-disk ring-buffer has wrapped around.
- * Newly created stream files will replace existing files. Since
- * live clients may be consuming existing files, the file about
- * to be replaced is unlinked in order to not overwrite its
- * content.
- */
- status = lttng_trace_chunk_unlink_file(trace_chunk,
- stream_path);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- PERROR("Failed to unlink stream file \"%s\" during trace file rotation",
- stream_path);
- /*
- * Don't abort if the file doesn't exist, it is
- * unexpected, but should not be a fatal error.
- */
- if (errno != ENOENT) {
- ret = -1;
- goto end;
- }
- }
- }
-
- status = lttng_trace_chunk_open_fs_handle(trace_chunk, stream_path,
- flags, mode, out_file, false);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ERR("Failed to open stream file \"%s\"", stream->channel_name);
- ret = -1;
- goto end;
- }
-end:
- return ret;
-}
-
-static int stream_rotate_data_file(struct relay_stream *stream)
-{
- int ret = 0;
-
- DBG("Rotating stream %" PRIu64 " data file with size %" PRIu64,
- stream->stream_handle, stream->tracefile_size_current);
-
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
-
- stream->tracefile_wrapped_around = false;
- stream->tracefile_current_index = 0;
-
- if (stream->ongoing_rotation.value.next_trace_chunk) {
- enum lttng_trace_chunk_status chunk_status;
-
- chunk_status = lttng_trace_chunk_create_subdirectory(
- stream->ongoing_rotation.value.next_trace_chunk,
- stream->path_name);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
-
- /* Rotate the data file. */
- ret = stream_create_data_output_file_from_trace_chunk(stream,
- stream->ongoing_rotation.value.next_trace_chunk,
- false, &stream->file);
- if (ret < 0) {
- ERR("Failed to rotate stream data file");
- goto end;
- }
- }
- DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
- __func__, stream->stream_handle, stream->tracefile_size_current);
- stream->tracefile_size_current = 0;
- stream->pos_after_last_complete_data_index = 0;
- stream->ongoing_rotation.value.data_rotated = true;
-
- if (stream->ongoing_rotation.value.index_rotated) {
- /* Rotation completed; reset its state. */
- stream_complete_rotation(stream);
- }
-end:
- return ret;
-}
-
-/*
- * If too much data has been written in a tracefile before we received the
- * rotation command, we have to move the excess data to the new tracefile and
- * perform the rotation. This can happen because the control and data
- * connections are separate, the indexes as well as the commands arrive from
- * the control connection and we have no control over the order so we could be
- * in a situation where too much data has been received on the data connection
- * before the rotation command on the control connection arrives.
- */
-static int rotate_truncate_stream(struct relay_stream *stream)
-{
- int ret;
- off_t lseek_ret, previous_stream_copy_origin;
- uint64_t copy_bytes_left, misplaced_data_size;
- bool acquired_reference;
- struct fs_handle *previous_stream_file = NULL;
- struct lttng_trace_chunk *previous_chunk = NULL;
-
- if (!LTTNG_OPTIONAL_GET(stream->ongoing_rotation).next_trace_chunk) {
- ERR("Protocol error encoutered in %s(): stream rotation "
- "sequence number is before the current sequence number "
- "and the next trace chunk is unset. Honoring this "
- "rotation command would result in data loss",
- __FUNCTION__);
- ret = -1;
- goto end;
- }
-
- ASSERT_LOCKED(stream->lock);
- /*
- * Acquire a reference to the current trace chunk to ensure
- * it is not reclaimed when `stream_rotate_data_file` is called.
- * Failing to do so would violate the contract of the trace
- * chunk API as an active file descriptor would outlive the
- * trace chunk.
- */
- acquired_reference = lttng_trace_chunk_get(stream->trace_chunk);
- LTTNG_ASSERT(acquired_reference);
- previous_chunk = stream->trace_chunk;
-
- /*
- * Steal the stream's reference to its stream_fd. A new
- * stream_fd will be created when the rotation completes and
- * the orinal stream_fd will be used to copy the "extra" data
- * to the new file.
- */
- LTTNG_ASSERT(stream->file);
- previous_stream_file = stream->file;
- stream->file = NULL;
-
- LTTNG_ASSERT(!stream->is_metadata);
- LTTNG_ASSERT(stream->tracefile_size_current >
- stream->pos_after_last_complete_data_index);
- misplaced_data_size = stream->tracefile_size_current -
- stream->pos_after_last_complete_data_index;
- copy_bytes_left = misplaced_data_size;
- previous_stream_copy_origin = stream->pos_after_last_complete_data_index;
-
- ret = stream_rotate_data_file(stream);
- if (ret) {
- goto end;
- }
-
- LTTNG_ASSERT(stream->file);
- /*
- * Seek the current tracefile to the position at which the rotation
- * should have occurred.
- */
- lseek_ret = fs_handle_seek(previous_stream_file, previous_stream_copy_origin, SEEK_SET);
- if (lseek_ret < 0) {
- PERROR("Failed to seek to offset %" PRIu64
- " while copying extra data received before a stream rotation",
- (uint64_t) previous_stream_copy_origin);
- ret = -1;
- goto end;
- }
-
- /* Move data from the old file to the new file. */
- while (copy_bytes_left) {
- ssize_t io_ret;
- char copy_buffer[FILE_IO_STACK_BUFFER_SIZE];
- const off_t copy_size_this_pass = min_t(
- off_t, copy_bytes_left, sizeof(copy_buffer));
-
- io_ret = fs_handle_read(previous_stream_file, copy_buffer,
- copy_size_this_pass);
- if (io_ret < (ssize_t) copy_size_this_pass) {
- if (io_ret == -1) {
- PERROR("Failed to read %" PRIu64
- " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
- copy_size_this_pass,
- __FUNCTION__, io_ret,
- stream->stream_handle);
- } else {
- ERR("Failed to read %" PRIu64
- " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
- copy_size_this_pass,
- __FUNCTION__, io_ret,
- stream->stream_handle);
- }
- ret = -1;
- goto end;
- }
-
- io_ret = fs_handle_write(
- stream->file, copy_buffer, copy_size_this_pass);
- if (io_ret < (ssize_t) copy_size_this_pass) {
- if (io_ret == -1) {
- PERROR("Failed to write %" PRIu64
- " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
- copy_size_this_pass,
- __FUNCTION__, io_ret,
- stream->stream_handle);
- } else {
- ERR("Failed to write %" PRIu64
- " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
- copy_size_this_pass,
- __FUNCTION__, io_ret,
- stream->stream_handle);
- }
- ret = -1;
- goto end;
- }
- copy_bytes_left -= copy_size_this_pass;
- }
-
- /* Truncate the file to get rid of the excess data. */
- ret = fs_handle_truncate(
- previous_stream_file, previous_stream_copy_origin);
- if (ret) {
- PERROR("Failed to truncate current stream file to offset %" PRIu64,
- previous_stream_copy_origin);
- goto end;
- }
-
- /*
- * Update the offset and FD of all the eventual indexes created by the
- * data connection before the rotation command arrived.
- */
- ret = relay_index_switch_all_files(stream);
- if (ret < 0) {
- ERR("Failed to rotate index file");
- goto end;
- }
-
- stream->tracefile_size_current = misplaced_data_size;
- /* Index and data contents are back in sync. */
- stream->pos_after_last_complete_data_index = 0;
- ret = 0;
-end:
- lttng_trace_chunk_put(previous_chunk);
- return ret;
-}
-
-/*
- * Check if a stream's data file (as opposed to index) should be rotated
- * (for session rotation).
- * Must be called with the stream lock held.
- *
- * Return 0 on success, a negative value on error.
- */
-static int try_rotate_stream_data(struct relay_stream *stream)
-{
- int ret = 0;
-
- if (caa_likely(!stream->ongoing_rotation.is_set)) {
- /* No rotation expected. */
- goto end;
- }
-
- if (stream->ongoing_rotation.value.data_rotated) {
- /* Rotation of the data file has already occurred. */
- goto end;
- }
-
- DBG("%s: Stream %" PRIu64
- " (rotate_at_index_packet_seq_num = %" PRIu64
- ", rotate_at_prev_data_net_seq = %" PRIu64
- ", prev_data_seq = %" PRIu64 ")",
- __func__, stream->stream_handle,
- stream->ongoing_rotation.value.packet_seq_num,
- stream->ongoing_rotation.value.prev_data_net_seq,
- stream->prev_data_seq);
-
- if (stream->prev_data_seq == -1ULL ||
- stream->ongoing_rotation.value.prev_data_net_seq == -1ULL ||
- stream->prev_data_seq <
- stream->ongoing_rotation.value.prev_data_net_seq) {
- /*
- * The next packet that will be written is not part of the next
- * chunk yet.
- */
- DBG("Stream %" PRIu64 " data not yet ready for rotation "
- "(rotate_at_index_packet_seq_num = %" PRIu64
- ", rotate_at_prev_data_net_seq = %" PRIu64
- ", prev_data_seq = %" PRIu64 ")",
- stream->stream_handle,
- stream->ongoing_rotation.value.packet_seq_num,
- stream->ongoing_rotation.value.prev_data_net_seq,
- stream->prev_data_seq);
- goto end;
- } else if (stream->prev_data_seq > stream->ongoing_rotation.value.prev_data_net_seq) {
- /*
- * prev_data_seq is checked here since indexes and rotation
- * commands are serialized with respect to each other.
- */
- DBG("Rotation after too much data has been written in tracefile "
- "for stream %" PRIu64 ", need to truncate before "
- "rotating", stream->stream_handle);
- ret = rotate_truncate_stream(stream);
- if (ret) {
- ERR("Failed to truncate stream");
- goto end;
- }
- } else {
- ret = stream_rotate_data_file(stream);
- }
-
-end:
- return ret;
-}
-
-/*
- * Close the current index file if it is open, and create a new one.
- *
- * Return 0 on success, -1 on error.
- */
-static int create_index_file(struct relay_stream *stream,
- struct lttng_trace_chunk *chunk)
-{
- int ret;
- uint32_t major, minor;
- char *index_subpath = NULL;
- enum lttng_trace_chunk_status status;
-
- ASSERT_LOCKED(stream->lock);
-
- /* Put ref on previous index_file. */
- if (stream->index_file) {
- lttng_index_file_put(stream->index_file);
- stream->index_file = NULL;
- }
- major = stream->trace->session->major;
- minor = stream->trace->session->minor;
-
- if (!chunk) {
- ret = 0;
- goto end;
- }
- ret = asprintf(&index_subpath, "%s/%s", stream->path_name,
- DEFAULT_INDEX_DIR);
- if (ret < 0) {
- goto end;
- }
-
- status = lttng_trace_chunk_create_subdirectory(chunk,
- index_subpath);
- free(index_subpath);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
- status = lttng_index_file_create_from_trace_chunk(
- chunk, stream->path_name,
- stream->channel_name, stream->tracefile_size,
- stream->tracefile_current_index,
- lttng_to_index_major(major, minor),
- lttng_to_index_minor(major, minor), true,
- &stream->index_file);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
-
- ret = 0;
-
-end:
- return ret;
-}
-
-/*
- * Check if a stream's index file should be rotated (for session rotation).
- * Must be called with the stream lock held.
- *
- * Return 0 on success, a negative value on error.
- */
-static int try_rotate_stream_index(struct relay_stream *stream)
-{
- int ret = 0;
-
- if (!stream->ongoing_rotation.is_set) {
- /* No rotation expected. */
- goto end;
- }
-
- if (stream->ongoing_rotation.value.index_rotated) {
- /* Rotation of the index has already occurred. */
- goto end;
- }
-
- DBG("%s: Stream %" PRIu64
- " (rotate_at_packet_seq_num = %" PRIu64
- ", received_packet_seq_num = "
- "(value = %" PRIu64 ", is_set = %" PRIu8 "))",
- __func__, stream->stream_handle,
- stream->ongoing_rotation.value.packet_seq_num,
- stream->received_packet_seq_num.value,
- stream->received_packet_seq_num.is_set);
-
- if (!stream->received_packet_seq_num.is_set ||
- LTTNG_OPTIONAL_GET(stream->received_packet_seq_num) + 1 <
- stream->ongoing_rotation.value.packet_seq_num) {
- DBG("Stream %" PRIu64 " index not yet ready for rotation "
- "(rotate_at_packet_seq_num = %" PRIu64
- ", received_packet_seq_num = "
- "(value = %" PRIu64 ", is_set = %" PRIu8 "))",
- stream->stream_handle,
- stream->ongoing_rotation.value.packet_seq_num,
- stream->received_packet_seq_num.value,
- stream->received_packet_seq_num.is_set);
- goto end;
- } else {
- /*
- * The next index belongs to the new trace chunk; rotate.
- * In overwrite mode, the packet seq num may jump over the
- * rotation position.
- */
- LTTNG_ASSERT(LTTNG_OPTIONAL_GET(stream->received_packet_seq_num) + 1 >=
- stream->ongoing_rotation.value.packet_seq_num);
- DBG("Rotating stream %" PRIu64 " index file",
- stream->stream_handle);
- if (stream->index_file) {
- lttng_index_file_put(stream->index_file);
- stream->index_file = NULL;
- }
- stream->ongoing_rotation.value.index_rotated = true;
-
- /*
- * Set the rotation pivot position for the data, now that we have the
- * net_seq_num matching the packet_seq_num index pivot position.
- */
- stream->ongoing_rotation.value.prev_data_net_seq =
- stream->prev_index_seq;
- if (stream->ongoing_rotation.value.data_rotated &&
- stream->ongoing_rotation.value.index_rotated) {
- /* Rotation completed; reset its state. */
- DBG("Rotation completed for stream %" PRIu64,
- stream->stream_handle);
- stream_complete_rotation(stream);
- }
- }
-
-end:
- return ret;
-}
-
-static int stream_set_trace_chunk(struct relay_stream *stream,
- struct lttng_trace_chunk *chunk)
-{
- int ret = 0;
- enum lttng_trace_chunk_status status;
- bool acquired_reference;
-
- status = lttng_trace_chunk_create_subdirectory(chunk,
- stream->path_name);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- ret = -1;
- goto end;
- }
-
- lttng_trace_chunk_put(stream->trace_chunk);
- acquired_reference = lttng_trace_chunk_get(chunk);
- LTTNG_ASSERT(acquired_reference);
- stream->trace_chunk = chunk;
-
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
- ret = stream_create_data_output_file_from_trace_chunk(stream, chunk,
- false, &stream->file);
-end:
- return ret;
-}
-
-/*
- * We keep ownership of path_name and channel_name.
- */
-struct relay_stream *stream_create(struct ctf_trace *trace,
- uint64_t stream_handle, char *path_name,
- char *channel_name, uint64_t tracefile_size,
- uint64_t tracefile_count)
-{
- int ret;
- struct relay_stream *stream = NULL;
- struct relay_session *session = trace->session;
- bool acquired_reference = false;
- struct lttng_trace_chunk *current_trace_chunk;
-
- stream = zmalloc(sizeof(struct relay_stream));
- if (stream == NULL) {
- PERROR("relay stream zmalloc");
- goto error_no_alloc;
- }
-
- stream->stream_handle = stream_handle;
- stream->prev_data_seq = -1ULL;
- stream->prev_index_seq = -1ULL;
- stream->last_net_seq_num = -1ULL;
- stream->ctf_stream_id = -1ULL;
- stream->tracefile_size = tracefile_size;
- stream->tracefile_count = tracefile_count;
- stream->path_name = path_name;
- stream->channel_name = channel_name;
- stream->beacon_ts_end = -1ULL;
- lttng_ht_node_init_u64(&stream->node, stream->stream_handle);
- pthread_mutex_init(&stream->lock, NULL);
- urcu_ref_init(&stream->ref);
- ctf_trace_get(trace);
- stream->trace = trace;
-
- pthread_mutex_lock(&trace->session->lock);
- current_trace_chunk = trace->session->current_trace_chunk;
- if (current_trace_chunk) {
- acquired_reference = lttng_trace_chunk_get(current_trace_chunk);
- }
- pthread_mutex_unlock(&trace->session->lock);
- if (!acquired_reference) {
- ERR("Cannot create stream for channel \"%s\" as a reference to the session's current trace chunk could not be acquired",
- channel_name);
- ret = -1;
- goto end;
- }
-
- stream->indexes_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
- if (!stream->indexes_ht) {
- ERR("Cannot created indexes_ht");
- ret = -1;
- goto end;
- }
-
- pthread_mutex_lock(&stream->lock);
- ret = stream_set_trace_chunk(stream, current_trace_chunk);
- pthread_mutex_unlock(&stream->lock);
- if (ret) {
- ERR("Failed to set the current trace chunk of session \"%s\" on newly created stream of channel \"%s\"",
- trace->session->session_name,
- stream->channel_name);
- ret = -1;
- goto end;
- }
- stream->tfa = tracefile_array_create(stream->tracefile_count);
- if (!stream->tfa) {
- ret = -1;
- goto end;
- }
-
- stream->is_metadata = !strcmp(stream->channel_name,
- DEFAULT_METADATA_NAME);
- stream->in_recv_list = true;
-
- /*
- * Add the stream in the recv list of the session. Once the end stream
- * message is received, all session streams are published.
- */
- pthread_mutex_lock(&session->recv_list_lock);
- cds_list_add_rcu(&stream->recv_node, &session->recv_list);
- session->stream_count++;
- pthread_mutex_unlock(&session->recv_list_lock);
-
- /*
- * Both in the ctf_trace object and the global stream ht since the data
- * side of the relayd does not have the concept of session.
- */
- lttng_ht_add_unique_u64(relay_streams_ht, &stream->node);
- stream->in_stream_ht = true;
-
- DBG("Relay new stream added %s with ID %" PRIu64, stream->channel_name,
- stream->stream_handle);
- ret = 0;
-
-end:
- if (ret) {
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
- stream_put(stream);
- stream = NULL;
- }
- if (acquired_reference) {
- lttng_trace_chunk_put(current_trace_chunk);
- }
- return stream;
-
-error_no_alloc:
- /*
- * path_name and channel_name need to be freed explicitly here
- * because we cannot rely on stream_put().
- */
- free(path_name);
- free(channel_name);
- return NULL;
-}
-
-/*
- * Called with the session lock held.
- */
-void stream_publish(struct relay_stream *stream)
-{
- struct relay_session *session;
-
- pthread_mutex_lock(&stream->lock);
- if (stream->published) {
- goto unlock;
- }
-
- session = stream->trace->session;
-
- pthread_mutex_lock(&session->recv_list_lock);
- if (stream->in_recv_list) {
- cds_list_del_rcu(&stream->recv_node);
- stream->in_recv_list = false;
- }
- pthread_mutex_unlock(&session->recv_list_lock);
-
- pthread_mutex_lock(&stream->trace->stream_list_lock);
- cds_list_add_rcu(&stream->stream_node, &stream->trace->stream_list);
- pthread_mutex_unlock(&stream->trace->stream_list_lock);
-
- stream->published = true;
-unlock:
- pthread_mutex_unlock(&stream->lock);
-}
-
-/*
- * Stream must be protected by holding the stream lock or by virtue of being
- * called from stream_destroy.
- */
-static void stream_unpublish(struct relay_stream *stream)
-{
- if (stream->in_stream_ht) {
- struct lttng_ht_iter iter;
- int ret;
-
- iter.iter.node = &stream->node.node;
- ret = lttng_ht_del(relay_streams_ht, &iter);
- LTTNG_ASSERT(!ret);
- stream->in_stream_ht = false;
- }
- if (stream->published) {
- pthread_mutex_lock(&stream->trace->stream_list_lock);
- cds_list_del_rcu(&stream->stream_node);
- pthread_mutex_unlock(&stream->trace->stream_list_lock);
- stream->published = false;
- }
-}
-
-static void stream_destroy(struct relay_stream *stream)
-{
- if (stream->indexes_ht) {
- /*
- * Calling lttng_ht_destroy in call_rcu worker thread so
- * we don't hold the RCU read-side lock while calling
- * it.
- */
- lttng_ht_destroy(stream->indexes_ht);
- }
- if (stream->tfa) {
- tracefile_array_destroy(stream->tfa);
- }
- free(stream->path_name);
- free(stream->channel_name);
- free(stream);
-}
-
-static void stream_destroy_rcu(struct rcu_head *rcu_head)
-{
- struct relay_stream *stream =
- caa_container_of(rcu_head, struct relay_stream, rcu_node);
-
- stream_destroy(stream);
-}
-
-/*
- * No need to take stream->lock since this is only called on the final
- * stream_put which ensures that a single thread may act on the stream.
- */
-static void stream_release(struct urcu_ref *ref)
-{
- struct relay_stream *stream =
- caa_container_of(ref, struct relay_stream, ref);
- struct relay_session *session;
-
- session = stream->trace->session;
-
- DBG("Releasing stream id %" PRIu64, stream->stream_handle);
-
- pthread_mutex_lock(&session->recv_list_lock);
- session->stream_count--;
- if (stream->in_recv_list) {
- cds_list_del_rcu(&stream->recv_node);
- stream->in_recv_list = false;
- }
- pthread_mutex_unlock(&session->recv_list_lock);
-
- stream_unpublish(stream);
-
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
- if (stream->index_file) {
- lttng_index_file_put(stream->index_file);
- stream->index_file = NULL;
- }
- if (stream->trace) {
- ctf_trace_put(stream->trace);
- stream->trace = NULL;
- }
- stream_complete_rotation(stream);
- lttng_trace_chunk_put(stream->trace_chunk);
- stream->trace_chunk = NULL;
-
- call_rcu(&stream->rcu_node, stream_destroy_rcu);
-}
-
-void stream_put(struct relay_stream *stream)
-{
- rcu_read_lock();
- LTTNG_ASSERT(stream->ref.refcount != 0);
- /*
- * Wait until we have processed all the stream packets before
- * actually putting our last stream reference.
- */
- urcu_ref_put(&stream->ref, stream_release);
- rcu_read_unlock();
-}
-
-int stream_set_pending_rotation(struct relay_stream *stream,
- struct lttng_trace_chunk *next_trace_chunk,
- uint64_t rotation_sequence_number)
-{
- int ret = 0;
- const struct relay_stream_rotation rotation = {
- .data_rotated = false,
- .index_rotated = false,
- .packet_seq_num = rotation_sequence_number,
- .prev_data_net_seq = -1ULL,
- .next_trace_chunk = next_trace_chunk,
- };
-
- if (stream->ongoing_rotation.is_set) {
- ERR("Attempted to set a pending rotation on a stream already being rotated (protocol error)");
- ret = -1;
- goto end;
- }
-
- if (next_trace_chunk) {
- const bool reference_acquired =
- lttng_trace_chunk_get(next_trace_chunk);
-
- LTTNG_ASSERT(reference_acquired);
- }
- LTTNG_OPTIONAL_SET(&stream->ongoing_rotation, rotation);
-
- DBG("Setting pending rotation: stream_id = %" PRIu64
- ", rotate_at_packet_seq_num = %" PRIu64,
- stream->stream_handle, rotation_sequence_number);
- if (stream->is_metadata) {
- /*
- * A metadata stream has no index; consider it already rotated.
- */
- stream->ongoing_rotation.value.index_rotated = true;
- if (next_trace_chunk) {
- /*
- * The metadata will be received again in the new chunk.
- */
- stream->metadata_received = 0;
- }
- ret = stream_rotate_data_file(stream);
- } else {
- ret = try_rotate_stream_index(stream);
- if (ret < 0) {
- goto end;
- }
-
- ret = try_rotate_stream_data(stream);
- if (ret < 0) {
- goto end;
- }
- }
-end:
- return ret;
-}
-
-void try_stream_close(struct relay_stream *stream)
-{
- bool session_aborted;
- struct relay_session *session = stream->trace->session;
-
- DBG("Trying to close stream %" PRIu64, stream->stream_handle);
-
- pthread_mutex_lock(&session->lock);
- session_aborted = session->aborted;
- pthread_mutex_unlock(&session->lock);
-
- pthread_mutex_lock(&stream->lock);
- /*
- * Can be called concurently by connection close and reception of last
- * pending data.
- */
- if (stream->closed) {
- pthread_mutex_unlock(&stream->lock);
- DBG("closing stream %" PRIu64 " aborted since it is already marked as closed", stream->stream_handle);
- return;
- }
-
- stream->close_requested = true;
-
- if (stream->last_net_seq_num == -1ULL) {
- /*
- * Handle connection close without explicit stream close
- * command.
- *
- * We can be clever about indexes partially received in
- * cases where we received the data socket part, but not
- * the control socket part: since we're currently closing
- * the stream on behalf of the control socket, we *know*
- * there won't be any more control information for this
- * socket. Therefore, we can destroy all indexes for
- * which we have received only the file descriptor (from
- * data socket). This takes care of consumerd crashes
- * between sending the data and control information for
- * a packet. Since those are sent in that order, we take
- * care of consumerd crashes.
- */
- DBG("relay_index_close_partial_fd");
- relay_index_close_partial_fd(stream);
- /*
- * Use the highest net_seq_num we currently have pending
- * As end of stream indicator. Leave last_net_seq_num
- * at -1ULL if we cannot find any index.
- */
- stream->last_net_seq_num = relay_index_find_last(stream);
- DBG("Updating stream->last_net_seq_num to %" PRIu64, stream->last_net_seq_num);
- /* Fall-through into the next check. */
- }
-
- if (stream->last_net_seq_num != -1ULL &&
- ((int64_t) (stream->prev_data_seq - stream->last_net_seq_num)) < 0
- && !session_aborted) {
- /*
- * Don't close since we still have data pending. This
- * handles cases where an explicit close command has
- * been received for this stream, and cases where the
- * connection has been closed, and we are awaiting for
- * index information from the data socket. It is
- * therefore expected that all the index fd information
- * we need has already been received on the control
- * socket. Matching index information from data socket
- * should be Expected Soon(TM).
- *
- * TODO: We should implement a timer to garbage collect
- * streams after a timeout to be resilient against a
- * consumerd implementation that would not match this
- * expected behavior.
- */
- pthread_mutex_unlock(&stream->lock);
- DBG("closing stream %" PRIu64 " aborted since it still has data pending", stream->stream_handle);
- return;
- }
- /*
- * We received all the indexes we can expect.
- */
- stream_unpublish(stream);
- stream->closed = true;
- /* Relay indexes are only used by the "consumer/sessiond" end. */
- relay_index_close_all(stream);
-
- /*
- * If we are closed by an application exiting (per-pid buffers),
- * we need to put our reference on the stream trace chunk right
- * away, because otherwise still holding the reference on the
- * trace chunk could allow a viewer stream (which holds a reference
- * to the stream) to postpone destroy waiting for the chunk to cease
- * to exist endlessly until the viewer is detached.
- */
-
- /* Put stream fd before put chunk. */
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
- if (stream->index_file) {
- lttng_index_file_put(stream->index_file);
- stream->index_file = NULL;
- }
- lttng_trace_chunk_put(stream->trace_chunk);
- stream->trace_chunk = NULL;
- pthread_mutex_unlock(&stream->lock);
- DBG("Succeeded in closing stream %" PRIu64, stream->stream_handle);
- stream_put(stream);
-}
-
-int stream_init_packet(struct relay_stream *stream, size_t packet_size,
- bool *file_rotated)
-{
- int ret = 0;
-
- ASSERT_LOCKED(stream->lock);
-
- if (!stream->file || !stream->trace_chunk) {
- ERR("Protocol error: received a packet for a stream that doesn't have a current trace chunk: stream_id = %" PRIu64 ", channel_name = %s",
- stream->stream_handle, stream->channel_name);
- ret = -1;
- goto end;
- }
-
- if (caa_likely(stream->tracefile_size == 0)) {
- /* No size limit set; nothing to check. */
- goto end;
- }
-
- /*
- * Check if writing the new packet would exceed the maximal file size.
- */
- if (caa_unlikely((stream->tracefile_size_current + packet_size) >
- stream->tracefile_size)) {
- const uint64_t new_file_index =
- (stream->tracefile_current_index + 1) %
- stream->tracefile_count;
-
- if (new_file_index < stream->tracefile_current_index) {
- stream->tracefile_wrapped_around = true;
- }
- DBG("New stream packet causes stream file rotation: stream_id = %" PRIu64
- ", current_file_size = %" PRIu64
- ", packet_size = %zu, current_file_index = %" PRIu64
- " new_file_index = %" PRIu64,
- stream->stream_handle,
- stream->tracefile_size_current, packet_size,
- stream->tracefile_current_index, new_file_index);
- tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_WRITE);
- stream->tracefile_current_index = new_file_index;
-
- if (stream->file) {
- fs_handle_close(stream->file);
- stream->file = NULL;
- }
- ret = stream_create_data_output_file_from_trace_chunk(stream,
- stream->trace_chunk, false, &stream->file);
- if (ret) {
- ERR("Failed to perform trace file rotation of stream %" PRIu64,
- stream->stream_handle);
- goto end;
- }
-
- /*
- * Reset current size because we just performed a stream
- * rotation.
- */
- DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
- __func__, stream->stream_handle, stream->tracefile_size_current);
- stream->tracefile_size_current = 0;
- *file_rotated = true;
- } else {
- *file_rotated = false;
- }
-end:
- return ret;
-}
-
-/* Note that the packet is not necessarily complete. */
-int stream_write(struct relay_stream *stream,
- const struct lttng_buffer_view *packet, size_t padding_len)
-{
- int ret = 0;
- ssize_t write_ret;
- size_t padding_to_write = padding_len;
- char padding_buffer[FILE_IO_STACK_BUFFER_SIZE];
-
- ASSERT_LOCKED(stream->lock);
- memset(padding_buffer, 0,
- min(sizeof(padding_buffer), padding_to_write));
-
- if (!stream->file || !stream->trace_chunk) {
- ERR("Protocol error: received a packet for a stream that doesn't have a current trace chunk: stream_id = %" PRIu64 ", channel_name = %s",
- stream->stream_handle, stream->channel_name);
- ret = -1;
- goto end;
- }
- if (packet) {
- write_ret = fs_handle_write(
- stream->file, packet->data, packet->size);
- if (write_ret != packet->size) {
- PERROR("Failed to write to stream file of %sstream %" PRIu64,
- stream->is_metadata ? "metadata " : "",
- stream->stream_handle);
- ret = -1;
- goto end;
- }
- }
-
- while (padding_to_write > 0) {
- const size_t padding_to_write_this_pass =
- min(padding_to_write, sizeof(padding_buffer));
-
- write_ret = fs_handle_write(stream->file, padding_buffer,
- padding_to_write_this_pass);
- if (write_ret != padding_to_write_this_pass) {
- PERROR("Failed to write padding to file of %sstream %" PRIu64,
- stream->is_metadata ? "metadata " : "",
- stream->stream_handle);
- ret = -1;
- goto end;
- }
- padding_to_write -= padding_to_write_this_pass;
- }
-
- if (stream->is_metadata) {
- size_t recv_len;
-
- recv_len = packet ? packet->size : 0;
- recv_len += padding_len;
- stream->metadata_received += recv_len;
- if (recv_len) {
- stream->no_new_metadata_notified = false;
- }
- }
-
- DBG("Wrote to %sstream %" PRIu64 ": data_length = %zu, padding_length = %zu",
- stream->is_metadata ? "metadata " : "",
- stream->stream_handle,
- packet ? packet->size : (size_t) 0, padding_len);
-end:
- return ret;
-}
-
-/*
- * Update index after receiving a packet for a data stream.
- *
- * Called with the stream lock held.
- *
- * Return 0 on success else a negative value.
- */
-int stream_update_index(struct relay_stream *stream, uint64_t net_seq_num,
- bool rotate_index, bool *flushed, uint64_t total_size)
-{
- int ret = 0;
- uint64_t data_offset;
- struct relay_index *index;
-
- LTTNG_ASSERT(stream->trace_chunk);
- ASSERT_LOCKED(stream->lock);
- /* Get data offset because we are about to update the index. */
- data_offset = htobe64(stream->tracefile_size_current);
-
- DBG("handle_index_data: stream %" PRIu64 " net_seq_num %" PRIu64 " data offset %" PRIu64,
- stream->stream_handle, net_seq_num, stream->tracefile_size_current);
-
- /*
- * Lookup for an existing index for that stream id/sequence
- * number. If it exists, the control thread has already received the
- * data for it, thus we need to write it to disk.
- */
- index = relay_index_get_by_id_or_create(stream, net_seq_num);
- if (!index) {
- ret = -1;
- goto end;
- }
-
- if (rotate_index || !stream->index_file) {
- ret = create_index_file(stream, stream->trace_chunk);
- if (ret) {
- ERR("Failed to create index file for stream %" PRIu64,
- stream->stream_handle);
- /* Put self-ref for this index due to error. */
- relay_index_put(index);
- index = NULL;
- goto end;
- }
- }
-
- if (relay_index_set_file(index, stream->index_file, data_offset)) {
- ret = -1;
- /* Put self-ref for this index due to error. */
- relay_index_put(index);
- index = NULL;
- goto end;
- }
-
- ret = relay_index_try_flush(index);
- if (ret == 0) {
- tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_READ);
- tracefile_array_commit_seq(stream->tfa, stream->index_received_seqcount);
- stream->index_received_seqcount++;
- LTTNG_OPTIONAL_SET(&stream->received_packet_seq_num,
- be64toh(index->index_data.packet_seq_num));
- *flushed = true;
- } else if (ret > 0) {
- index->total_size = total_size;
- /* No flush. */
- ret = 0;
- } else {
- /*
- * ret < 0
- *
- * relay_index_try_flush is responsible for the self-reference
- * put of the index object on error.
- */
- ERR("relay_index_try_flush error %d", ret);
- ret = -1;
- }
-end:
- return ret;
-}
-
-int stream_complete_packet(struct relay_stream *stream, size_t packet_total_size,
- uint64_t sequence_number, bool index_flushed)
-{
- int ret = 0;
-
- ASSERT_LOCKED(stream->lock);
-
- stream->tracefile_size_current += packet_total_size;
- if (index_flushed) {
- stream->pos_after_last_complete_data_index =
- stream->tracefile_size_current;
- stream->prev_index_seq = sequence_number;
- ret = try_rotate_stream_index(stream);
- if (ret < 0) {
- goto end;
- }
- }
-
- stream->prev_data_seq = sequence_number;
- ret = try_rotate_stream_data(stream);
-
-end:
- return ret;
-}
-
-int stream_add_index(struct relay_stream *stream,
- const struct lttcomm_relayd_index *index_info)
-{
- int ret = 0;
- struct relay_index *index;
-
- ASSERT_LOCKED(stream->lock);
-
- DBG("stream_add_index for stream %" PRIu64, stream->stream_handle);
-
- /* Live beacon handling */
- if (index_info->packet_size == 0) {
- DBG("Received live beacon for stream %" PRIu64,
- stream->stream_handle);
-
- /*
- * Only flag a stream inactive when it has already
- * received data and no indexes are in flight.
- */
- if (stream->index_received_seqcount > 0
- && stream->indexes_in_flight == 0) {
- stream->beacon_ts_end = index_info->timestamp_end;
- }
- ret = 0;
- goto end;
- } else {
- stream->beacon_ts_end = -1ULL;
- }
-
- if (stream->ctf_stream_id == -1ULL) {
- stream->ctf_stream_id = index_info->stream_id;
- }
-
- index = relay_index_get_by_id_or_create(stream, index_info->net_seq_num);
- if (!index) {
- ret = -1;
- ERR("Failed to get or create index %" PRIu64,
- index_info->net_seq_num);
- goto end;
- }
- if (relay_index_set_control_data(index, index_info,
- stream->trace->session->minor)) {
- ERR("set_index_control_data error");
- relay_index_put(index);
- ret = -1;
- goto end;
- }
- ret = relay_index_try_flush(index);
- if (ret == 0) {
- tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_READ);
- tracefile_array_commit_seq(stream->tfa, stream->index_received_seqcount);
- stream->index_received_seqcount++;
- stream->pos_after_last_complete_data_index += index->total_size;
- stream->prev_index_seq = index_info->net_seq_num;
- LTTNG_OPTIONAL_SET(&stream->received_packet_seq_num,
- index_info->packet_seq_num);
-
- ret = try_rotate_stream_index(stream);
- if (ret < 0) {
- goto end;
- }
- ret = try_rotate_stream_data(stream);
- if (ret < 0) {
- goto end;
- }
- } else if (ret > 0) {
- /* no flush. */
- ret = 0;
- } else {
- /*
- * ret < 0
- *
- * relay_index_try_flush is responsible for the self-reference
- * put of the index object on error.
- */
- ERR("relay_index_try_flush error %d", ret);
- ret = -1;
- }
-end:
- return ret;
-}
-
-static void print_stream_indexes(struct relay_stream *stream)
-{
- struct lttng_ht_iter iter;
- struct relay_index *index;
-
- rcu_read_lock();
- cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter, index,
- index_n.node) {
- DBG("index %p net_seq_num %" PRIu64 " refcount %ld"
- " stream %" PRIu64 " trace %" PRIu64
- " session %" PRIu64,
- index,
- index->index_n.key,
- stream->ref.refcount,
- index->stream->stream_handle,
- index->stream->trace->id,
- index->stream->trace->session->id);
- }
- rcu_read_unlock();
-}
-
-int stream_reset_file(struct relay_stream *stream)
-{
- ASSERT_LOCKED(stream->lock);
-
- if (stream->file) {
- int ret;
-
- ret = fs_handle_close(stream->file);
- if (ret) {
- ERR("Failed to close stream file handle: channel name = \"%s\", id = %" PRIu64,
- stream->channel_name,
- stream->stream_handle);
- }
- stream->file = NULL;
- }
-
- DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
- __func__, stream->stream_handle, stream->tracefile_size_current);
- stream->tracefile_size_current = 0;
- stream->prev_data_seq = 0;
- stream->prev_index_seq = 0;
- /* Note that this does not reset the tracefile array. */
- stream->tracefile_current_index = 0;
- stream->pos_after_last_complete_data_index = 0;
-
- return stream_create_data_output_file_from_trace_chunk(stream,
- stream->trace_chunk, true, &stream->file);
-}
-
-void print_relay_streams(void)
-{
- struct lttng_ht_iter iter;
- struct relay_stream *stream;
-
- if (!relay_streams_ht) {
- return;
- }
-
- rcu_read_lock();
- cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
- node.node) {
- if (!stream_get(stream)) {
- continue;
- }
- DBG("stream %p refcount %ld stream %" PRIu64 " trace %" PRIu64
- " session %" PRIu64,
- stream,
- stream->ref.refcount,
- stream->stream_handle,
- stream->trace->id,
- stream->trace->session->id);
- print_stream_indexes(stream);
- stream_put(stream);
- }
- rcu_read_unlock();
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <algorithm>
+#include <common/common.h>
+#include <common/defaults.h>
+#include <common/fs-handle.h>
+#include <common/sessiond-comm/relayd.h>
+#include <common/utils.h>
+#include <sys/stat.h>
+#include <urcu/rculist.h>
+
+#include "lttng-relayd.h"
+#include "index.h"
+#include "stream.h"
+#include "viewer-stream.h"
+
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define FILE_IO_STACK_BUFFER_SIZE 65536
+
+/* Should be called with RCU read-side lock held. */
+bool stream_get(struct relay_stream *stream)
+{
+ return urcu_ref_get_unless_zero(&stream->ref);
+}
+
+/*
+ * Get stream from stream id from the streams hash table. Return stream
+ * if found else NULL. A stream reference is taken when a stream is
+ * returned. stream_put() must be called on that stream.
+ */
+struct relay_stream *stream_get_by_id(uint64_t stream_id)
+{
+ struct lttng_ht_node_u64 *node;
+ struct lttng_ht_iter iter;
+ struct relay_stream *stream = NULL;
+
+ rcu_read_lock();
+ lttng_ht_lookup(relay_streams_ht, &stream_id, &iter);
+ node = lttng_ht_iter_get_node_u64(&iter);
+ if (!node) {
+ DBG("Relay stream %" PRIu64 " not found", stream_id);
+ goto end;
+ }
+ stream = caa_container_of(node, struct relay_stream, node);
+ if (!stream_get(stream)) {
+ stream = NULL;
+ }
+end:
+ rcu_read_unlock();
+ return stream;
+}
+
+static void stream_complete_rotation(struct relay_stream *stream)
+{
+ DBG("Rotation completed for stream %" PRIu64, stream->stream_handle);
+ if (stream->ongoing_rotation.value.next_trace_chunk) {
+ tracefile_array_reset(stream->tfa);
+ tracefile_array_commit_seq(stream->tfa,
+ stream->index_received_seqcount);
+ }
+ lttng_trace_chunk_put(stream->trace_chunk);
+ stream->trace_chunk = stream->ongoing_rotation.value.next_trace_chunk;
+ stream->ongoing_rotation = (typeof(stream->ongoing_rotation)) {};
+ stream->completed_rotation_count++;
+}
+
+static int stream_create_data_output_file_from_trace_chunk(
+ struct relay_stream *stream,
+ struct lttng_trace_chunk *trace_chunk,
+ bool force_unlink,
+ struct fs_handle **out_file)
+{
+ int ret;
+ char stream_path[LTTNG_PATH_MAX];
+ enum lttng_trace_chunk_status status;
+ const int flags = O_RDWR | O_CREAT | O_TRUNC;
+ const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+
+ ASSERT_LOCKED(stream->lock);
+
+ ret = utils_stream_file_path(stream->path_name, stream->channel_name,
+ stream->tracefile_size, stream->tracefile_current_index,
+ NULL, stream_path, sizeof(stream_path));
+ if (ret < 0) {
+ goto end;
+ }
+
+ if (stream->tracefile_wrapped_around || force_unlink) {
+ /*
+ * The on-disk ring-buffer has wrapped around.
+ * Newly created stream files will replace existing files. Since
+ * live clients may be consuming existing files, the file about
+ * to be replaced is unlinked in order to not overwrite its
+ * content.
+ */
+ status = (lttng_trace_chunk_status) lttng_trace_chunk_unlink_file(trace_chunk,
+ stream_path);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ PERROR("Failed to unlink stream file \"%s\" during trace file rotation",
+ stream_path);
+ /*
+ * Don't abort if the file doesn't exist, it is
+ * unexpected, but should not be a fatal error.
+ */
+ if (errno != ENOENT) {
+ ret = -1;
+ goto end;
+ }
+ }
+ }
+
+ status = lttng_trace_chunk_open_fs_handle(trace_chunk, stream_path,
+ flags, mode, out_file, false);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ERR("Failed to open stream file \"%s\"", stream->channel_name);
+ ret = -1;
+ goto end;
+ }
+end:
+ return ret;
+}
+
+static int stream_rotate_data_file(struct relay_stream *stream)
+{
+ int ret = 0;
+
+ DBG("Rotating stream %" PRIu64 " data file with size %" PRIu64,
+ stream->stream_handle, stream->tracefile_size_current);
+
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+
+ stream->tracefile_wrapped_around = false;
+ stream->tracefile_current_index = 0;
+
+ if (stream->ongoing_rotation.value.next_trace_chunk) {
+ enum lttng_trace_chunk_status chunk_status;
+
+ chunk_status = lttng_trace_chunk_create_subdirectory(
+ stream->ongoing_rotation.value.next_trace_chunk,
+ stream->path_name);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+
+ /* Rotate the data file. */
+ ret = stream_create_data_output_file_from_trace_chunk(stream,
+ stream->ongoing_rotation.value.next_trace_chunk,
+ false, &stream->file);
+ if (ret < 0) {
+ ERR("Failed to rotate stream data file");
+ goto end;
+ }
+ }
+ DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
+ __func__, stream->stream_handle, stream->tracefile_size_current);
+ stream->tracefile_size_current = 0;
+ stream->pos_after_last_complete_data_index = 0;
+ stream->ongoing_rotation.value.data_rotated = true;
+
+ if (stream->ongoing_rotation.value.index_rotated) {
+ /* Rotation completed; reset its state. */
+ stream_complete_rotation(stream);
+ }
+end:
+ return ret;
+}
+
+/*
+ * If too much data has been written in a tracefile before we received the
+ * rotation command, we have to move the excess data to the new tracefile and
+ * perform the rotation. This can happen because the control and data
+ * connections are separate, the indexes as well as the commands arrive from
+ * the control connection and we have no control over the order so we could be
+ * in a situation where too much data has been received on the data connection
+ * before the rotation command on the control connection arrives.
+ */
+static int rotate_truncate_stream(struct relay_stream *stream)
+{
+ int ret;
+ off_t lseek_ret, previous_stream_copy_origin;
+ uint64_t copy_bytes_left, misplaced_data_size;
+ bool acquired_reference;
+ struct fs_handle *previous_stream_file = NULL;
+ struct lttng_trace_chunk *previous_chunk = NULL;
+
+ if (!LTTNG_OPTIONAL_GET(stream->ongoing_rotation).next_trace_chunk) {
+ ERR("Protocol error encoutered in %s(): stream rotation "
+ "sequence number is before the current sequence number "
+ "and the next trace chunk is unset. Honoring this "
+ "rotation command would result in data loss",
+ __FUNCTION__);
+ ret = -1;
+ goto end;
+ }
+
+ ASSERT_LOCKED(stream->lock);
+ /*
+ * Acquire a reference to the current trace chunk to ensure
+ * it is not reclaimed when `stream_rotate_data_file` is called.
+ * Failing to do so would violate the contract of the trace
+ * chunk API as an active file descriptor would outlive the
+ * trace chunk.
+ */
+ acquired_reference = lttng_trace_chunk_get(stream->trace_chunk);
+ LTTNG_ASSERT(acquired_reference);
+ previous_chunk = stream->trace_chunk;
+
+ /*
+ * Steal the stream's reference to its stream_fd. A new
+ * stream_fd will be created when the rotation completes and
+ * the orinal stream_fd will be used to copy the "extra" data
+ * to the new file.
+ */
+ LTTNG_ASSERT(stream->file);
+ previous_stream_file = stream->file;
+ stream->file = NULL;
+
+ LTTNG_ASSERT(!stream->is_metadata);
+ LTTNG_ASSERT(stream->tracefile_size_current >
+ stream->pos_after_last_complete_data_index);
+ misplaced_data_size = stream->tracefile_size_current -
+ stream->pos_after_last_complete_data_index;
+ copy_bytes_left = misplaced_data_size;
+ previous_stream_copy_origin = stream->pos_after_last_complete_data_index;
+
+ ret = stream_rotate_data_file(stream);
+ if (ret) {
+ goto end;
+ }
+
+ LTTNG_ASSERT(stream->file);
+ /*
+ * Seek the current tracefile to the position at which the rotation
+ * should have occurred.
+ */
+ lseek_ret = fs_handle_seek(previous_stream_file, previous_stream_copy_origin, SEEK_SET);
+ if (lseek_ret < 0) {
+ PERROR("Failed to seek to offset %" PRIu64
+ " while copying extra data received before a stream rotation",
+ (uint64_t) previous_stream_copy_origin);
+ ret = -1;
+ goto end;
+ }
+
+ /* Move data from the old file to the new file. */
+ while (copy_bytes_left) {
+ ssize_t io_ret;
+ char copy_buffer[FILE_IO_STACK_BUFFER_SIZE];
+ const off_t copy_size_this_pass = std::min(copy_bytes_left, sizeof(copy_buffer));
+
+ io_ret = fs_handle_read(previous_stream_file, copy_buffer,
+ copy_size_this_pass);
+ if (io_ret < (ssize_t) copy_size_this_pass) {
+ if (io_ret == -1) {
+ PERROR("Failed to read %" PRIu64
+ " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
+ copy_size_this_pass,
+ __FUNCTION__, io_ret,
+ stream->stream_handle);
+ } else {
+ ERR("Failed to read %" PRIu64
+ " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
+ copy_size_this_pass,
+ __FUNCTION__, io_ret,
+ stream->stream_handle);
+ }
+ ret = -1;
+ goto end;
+ }
+
+ io_ret = fs_handle_write(
+ stream->file, copy_buffer, copy_size_this_pass);
+ if (io_ret < (ssize_t) copy_size_this_pass) {
+ if (io_ret == -1) {
+ PERROR("Failed to write %" PRIu64
+ " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
+ copy_size_this_pass,
+ __FUNCTION__, io_ret,
+ stream->stream_handle);
+ } else {
+ ERR("Failed to write %" PRIu64
+ " bytes from previous stream file in %s(), returned %zi: stream id = %" PRIu64,
+ copy_size_this_pass,
+ __FUNCTION__, io_ret,
+ stream->stream_handle);
+ }
+ ret = -1;
+ goto end;
+ }
+ copy_bytes_left -= copy_size_this_pass;
+ }
+
+ /* Truncate the file to get rid of the excess data. */
+ ret = fs_handle_truncate(
+ previous_stream_file, previous_stream_copy_origin);
+ if (ret) {
+ PERROR("Failed to truncate current stream file to offset %" PRIu64,
+ previous_stream_copy_origin);
+ goto end;
+ }
+
+ /*
+ * Update the offset and FD of all the eventual indexes created by the
+ * data connection before the rotation command arrived.
+ */
+ ret = relay_index_switch_all_files(stream);
+ if (ret < 0) {
+ ERR("Failed to rotate index file");
+ goto end;
+ }
+
+ stream->tracefile_size_current = misplaced_data_size;
+ /* Index and data contents are back in sync. */
+ stream->pos_after_last_complete_data_index = 0;
+ ret = 0;
+end:
+ lttng_trace_chunk_put(previous_chunk);
+ return ret;
+}
+
+/*
+ * Check if a stream's data file (as opposed to index) should be rotated
+ * (for session rotation).
+ * Must be called with the stream lock held.
+ *
+ * Return 0 on success, a negative value on error.
+ */
+static int try_rotate_stream_data(struct relay_stream *stream)
+{
+ int ret = 0;
+
+ if (caa_likely(!stream->ongoing_rotation.is_set)) {
+ /* No rotation expected. */
+ goto end;
+ }
+
+ if (stream->ongoing_rotation.value.data_rotated) {
+ /* Rotation of the data file has already occurred. */
+ goto end;
+ }
+
+ DBG("%s: Stream %" PRIu64
+ " (rotate_at_index_packet_seq_num = %" PRIu64
+ ", rotate_at_prev_data_net_seq = %" PRIu64
+ ", prev_data_seq = %" PRIu64 ")",
+ __func__, stream->stream_handle,
+ stream->ongoing_rotation.value.packet_seq_num,
+ stream->ongoing_rotation.value.prev_data_net_seq,
+ stream->prev_data_seq);
+
+ if (stream->prev_data_seq == -1ULL ||
+ stream->ongoing_rotation.value.prev_data_net_seq == -1ULL ||
+ stream->prev_data_seq <
+ stream->ongoing_rotation.value.prev_data_net_seq) {
+ /*
+ * The next packet that will be written is not part of the next
+ * chunk yet.
+ */
+ DBG("Stream %" PRIu64 " data not yet ready for rotation "
+ "(rotate_at_index_packet_seq_num = %" PRIu64
+ ", rotate_at_prev_data_net_seq = %" PRIu64
+ ", prev_data_seq = %" PRIu64 ")",
+ stream->stream_handle,
+ stream->ongoing_rotation.value.packet_seq_num,
+ stream->ongoing_rotation.value.prev_data_net_seq,
+ stream->prev_data_seq);
+ goto end;
+ } else if (stream->prev_data_seq > stream->ongoing_rotation.value.prev_data_net_seq) {
+ /*
+ * prev_data_seq is checked here since indexes and rotation
+ * commands are serialized with respect to each other.
+ */
+ DBG("Rotation after too much data has been written in tracefile "
+ "for stream %" PRIu64 ", need to truncate before "
+ "rotating", stream->stream_handle);
+ ret = rotate_truncate_stream(stream);
+ if (ret) {
+ ERR("Failed to truncate stream");
+ goto end;
+ }
+ } else {
+ ret = stream_rotate_data_file(stream);
+ }
+
+end:
+ return ret;
+}
+
+/*
+ * Close the current index file if it is open, and create a new one.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int create_index_file(struct relay_stream *stream,
+ struct lttng_trace_chunk *chunk)
+{
+ int ret;
+ uint32_t major, minor;
+ char *index_subpath = NULL;
+ enum lttng_trace_chunk_status status;
+
+ ASSERT_LOCKED(stream->lock);
+
+ /* Put ref on previous index_file. */
+ if (stream->index_file) {
+ lttng_index_file_put(stream->index_file);
+ stream->index_file = NULL;
+ }
+ major = stream->trace->session->major;
+ minor = stream->trace->session->minor;
+
+ if (!chunk) {
+ ret = 0;
+ goto end;
+ }
+ ret = asprintf(&index_subpath, "%s/%s", stream->path_name,
+ DEFAULT_INDEX_DIR);
+ if (ret < 0) {
+ goto end;
+ }
+
+ status = lttng_trace_chunk_create_subdirectory(chunk,
+ index_subpath);
+ free(index_subpath);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+ status = lttng_index_file_create_from_trace_chunk(
+ chunk, stream->path_name,
+ stream->channel_name, stream->tracefile_size,
+ stream->tracefile_current_index,
+ lttng_to_index_major(major, minor),
+ lttng_to_index_minor(major, minor), true,
+ &stream->index_file);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ return ret;
+}
+
+/*
+ * Check if a stream's index file should be rotated (for session rotation).
+ * Must be called with the stream lock held.
+ *
+ * Return 0 on success, a negative value on error.
+ */
+static int try_rotate_stream_index(struct relay_stream *stream)
+{
+ int ret = 0;
+
+ if (!stream->ongoing_rotation.is_set) {
+ /* No rotation expected. */
+ goto end;
+ }
+
+ if (stream->ongoing_rotation.value.index_rotated) {
+ /* Rotation of the index has already occurred. */
+ goto end;
+ }
+
+ DBG("%s: Stream %" PRIu64
+ " (rotate_at_packet_seq_num = %" PRIu64
+ ", received_packet_seq_num = "
+ "(value = %" PRIu64 ", is_set = %" PRIu8 "))",
+ __func__, stream->stream_handle,
+ stream->ongoing_rotation.value.packet_seq_num,
+ stream->received_packet_seq_num.value,
+ stream->received_packet_seq_num.is_set);
+
+ if (!stream->received_packet_seq_num.is_set ||
+ LTTNG_OPTIONAL_GET(stream->received_packet_seq_num) + 1 <
+ stream->ongoing_rotation.value.packet_seq_num) {
+ DBG("Stream %" PRIu64 " index not yet ready for rotation "
+ "(rotate_at_packet_seq_num = %" PRIu64
+ ", received_packet_seq_num = "
+ "(value = %" PRIu64 ", is_set = %" PRIu8 "))",
+ stream->stream_handle,
+ stream->ongoing_rotation.value.packet_seq_num,
+ stream->received_packet_seq_num.value,
+ stream->received_packet_seq_num.is_set);
+ goto end;
+ } else {
+ /*
+ * The next index belongs to the new trace chunk; rotate.
+ * In overwrite mode, the packet seq num may jump over the
+ * rotation position.
+ */
+ LTTNG_ASSERT(LTTNG_OPTIONAL_GET(stream->received_packet_seq_num) + 1 >=
+ stream->ongoing_rotation.value.packet_seq_num);
+ DBG("Rotating stream %" PRIu64 " index file",
+ stream->stream_handle);
+ if (stream->index_file) {
+ lttng_index_file_put(stream->index_file);
+ stream->index_file = NULL;
+ }
+ stream->ongoing_rotation.value.index_rotated = true;
+
+ /*
+ * Set the rotation pivot position for the data, now that we have the
+ * net_seq_num matching the packet_seq_num index pivot position.
+ */
+ stream->ongoing_rotation.value.prev_data_net_seq =
+ stream->prev_index_seq;
+ if (stream->ongoing_rotation.value.data_rotated &&
+ stream->ongoing_rotation.value.index_rotated) {
+ /* Rotation completed; reset its state. */
+ DBG("Rotation completed for stream %" PRIu64,
+ stream->stream_handle);
+ stream_complete_rotation(stream);
+ }
+ }
+
+end:
+ return ret;
+}
+
+static int stream_set_trace_chunk(struct relay_stream *stream,
+ struct lttng_trace_chunk *chunk)
+{
+ int ret = 0;
+ enum lttng_trace_chunk_status status;
+ bool acquired_reference;
+
+ status = lttng_trace_chunk_create_subdirectory(chunk,
+ stream->path_name);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ ret = -1;
+ goto end;
+ }
+
+ lttng_trace_chunk_put(stream->trace_chunk);
+ acquired_reference = lttng_trace_chunk_get(chunk);
+ LTTNG_ASSERT(acquired_reference);
+ stream->trace_chunk = chunk;
+
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+ ret = stream_create_data_output_file_from_trace_chunk(stream, chunk,
+ false, &stream->file);
+end:
+ return ret;
+}
+
+/*
+ * We keep ownership of path_name and channel_name.
+ */
+struct relay_stream *stream_create(struct ctf_trace *trace,
+ uint64_t stream_handle, char *path_name,
+ char *channel_name, uint64_t tracefile_size,
+ uint64_t tracefile_count)
+{
+ int ret;
+ struct relay_stream *stream = NULL;
+ struct relay_session *session = trace->session;
+ bool acquired_reference = false;
+ struct lttng_trace_chunk *current_trace_chunk;
+
+ stream = (relay_stream *) zmalloc(sizeof(struct relay_stream));
+ if (stream == NULL) {
+ PERROR("relay stream zmalloc");
+ goto error_no_alloc;
+ }
+
+ stream->stream_handle = stream_handle;
+ stream->prev_data_seq = -1ULL;
+ stream->prev_index_seq = -1ULL;
+ stream->last_net_seq_num = -1ULL;
+ stream->ctf_stream_id = -1ULL;
+ stream->tracefile_size = tracefile_size;
+ stream->tracefile_count = tracefile_count;
+ stream->path_name = path_name;
+ stream->channel_name = channel_name;
+ stream->beacon_ts_end = -1ULL;
+ lttng_ht_node_init_u64(&stream->node, stream->stream_handle);
+ pthread_mutex_init(&stream->lock, NULL);
+ urcu_ref_init(&stream->ref);
+ ctf_trace_get(trace);
+ stream->trace = trace;
+
+ pthread_mutex_lock(&trace->session->lock);
+ current_trace_chunk = trace->session->current_trace_chunk;
+ if (current_trace_chunk) {
+ acquired_reference = lttng_trace_chunk_get(current_trace_chunk);
+ }
+ pthread_mutex_unlock(&trace->session->lock);
+ if (!acquired_reference) {
+ ERR("Cannot create stream for channel \"%s\" as a reference to the session's current trace chunk could not be acquired",
+ channel_name);
+ ret = -1;
+ goto end;
+ }
+
+ stream->indexes_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64);
+ if (!stream->indexes_ht) {
+ ERR("Cannot created indexes_ht");
+ ret = -1;
+ goto end;
+ }
+
+ pthread_mutex_lock(&stream->lock);
+ ret = stream_set_trace_chunk(stream, current_trace_chunk);
+ pthread_mutex_unlock(&stream->lock);
+ if (ret) {
+ ERR("Failed to set the current trace chunk of session \"%s\" on newly created stream of channel \"%s\"",
+ trace->session->session_name,
+ stream->channel_name);
+ ret = -1;
+ goto end;
+ }
+ stream->tfa = tracefile_array_create(stream->tracefile_count);
+ if (!stream->tfa) {
+ ret = -1;
+ goto end;
+ }
+
+ stream->is_metadata = !strcmp(stream->channel_name,
+ DEFAULT_METADATA_NAME);
+ stream->in_recv_list = true;
+
+ /*
+ * Add the stream in the recv list of the session. Once the end stream
+ * message is received, all session streams are published.
+ */
+ pthread_mutex_lock(&session->recv_list_lock);
+ cds_list_add_rcu(&stream->recv_node, &session->recv_list);
+ session->stream_count++;
+ pthread_mutex_unlock(&session->recv_list_lock);
+
+ /*
+ * Both in the ctf_trace object and the global stream ht since the data
+ * side of the relayd does not have the concept of session.
+ */
+ lttng_ht_add_unique_u64(relay_streams_ht, &stream->node);
+ stream->in_stream_ht = true;
+
+ DBG("Relay new stream added %s with ID %" PRIu64, stream->channel_name,
+ stream->stream_handle);
+ ret = 0;
+
+end:
+ if (ret) {
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+ stream_put(stream);
+ stream = NULL;
+ }
+ if (acquired_reference) {
+ lttng_trace_chunk_put(current_trace_chunk);
+ }
+ return stream;
+
+error_no_alloc:
+ /*
+ * path_name and channel_name need to be freed explicitly here
+ * because we cannot rely on stream_put().
+ */
+ free(path_name);
+ free(channel_name);
+ return NULL;
+}
+
+/*
+ * Called with the session lock held.
+ */
+void stream_publish(struct relay_stream *stream)
+{
+ struct relay_session *session;
+
+ pthread_mutex_lock(&stream->lock);
+ if (stream->published) {
+ goto unlock;
+ }
+
+ session = stream->trace->session;
+
+ pthread_mutex_lock(&session->recv_list_lock);
+ if (stream->in_recv_list) {
+ cds_list_del_rcu(&stream->recv_node);
+ stream->in_recv_list = false;
+ }
+ pthread_mutex_unlock(&session->recv_list_lock);
+
+ pthread_mutex_lock(&stream->trace->stream_list_lock);
+ cds_list_add_rcu(&stream->stream_node, &stream->trace->stream_list);
+ pthread_mutex_unlock(&stream->trace->stream_list_lock);
+
+ stream->published = true;
+unlock:
+ pthread_mutex_unlock(&stream->lock);
+}
+
+/*
+ * Stream must be protected by holding the stream lock or by virtue of being
+ * called from stream_destroy.
+ */
+static void stream_unpublish(struct relay_stream *stream)
+{
+ if (stream->in_stream_ht) {
+ struct lttng_ht_iter iter;
+ int ret;
+
+ iter.iter.node = &stream->node.node;
+ ret = lttng_ht_del(relay_streams_ht, &iter);
+ LTTNG_ASSERT(!ret);
+ stream->in_stream_ht = false;
+ }
+ if (stream->published) {
+ pthread_mutex_lock(&stream->trace->stream_list_lock);
+ cds_list_del_rcu(&stream->stream_node);
+ pthread_mutex_unlock(&stream->trace->stream_list_lock);
+ stream->published = false;
+ }
+}
+
+static void stream_destroy(struct relay_stream *stream)
+{
+ if (stream->indexes_ht) {
+ /*
+ * Calling lttng_ht_destroy in call_rcu worker thread so
+ * we don't hold the RCU read-side lock while calling
+ * it.
+ */
+ lttng_ht_destroy(stream->indexes_ht);
+ }
+ if (stream->tfa) {
+ tracefile_array_destroy(stream->tfa);
+ }
+ free(stream->path_name);
+ free(stream->channel_name);
+ free(stream);
+}
+
+static void stream_destroy_rcu(struct rcu_head *rcu_head)
+{
+ struct relay_stream *stream =
+ caa_container_of(rcu_head, struct relay_stream, rcu_node);
+
+ stream_destroy(stream);
+}
+
+/*
+ * No need to take stream->lock since this is only called on the final
+ * stream_put which ensures that a single thread may act on the stream.
+ */
+static void stream_release(struct urcu_ref *ref)
+{
+ struct relay_stream *stream =
+ caa_container_of(ref, struct relay_stream, ref);
+ struct relay_session *session;
+
+ session = stream->trace->session;
+
+ DBG("Releasing stream id %" PRIu64, stream->stream_handle);
+
+ pthread_mutex_lock(&session->recv_list_lock);
+ session->stream_count--;
+ if (stream->in_recv_list) {
+ cds_list_del_rcu(&stream->recv_node);
+ stream->in_recv_list = false;
+ }
+ pthread_mutex_unlock(&session->recv_list_lock);
+
+ stream_unpublish(stream);
+
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+ if (stream->index_file) {
+ lttng_index_file_put(stream->index_file);
+ stream->index_file = NULL;
+ }
+ if (stream->trace) {
+ ctf_trace_put(stream->trace);
+ stream->trace = NULL;
+ }
+ stream_complete_rotation(stream);
+ lttng_trace_chunk_put(stream->trace_chunk);
+ stream->trace_chunk = NULL;
+
+ call_rcu(&stream->rcu_node, stream_destroy_rcu);
+}
+
+void stream_put(struct relay_stream *stream)
+{
+ rcu_read_lock();
+ LTTNG_ASSERT(stream->ref.refcount != 0);
+ /*
+ * Wait until we have processed all the stream packets before
+ * actually putting our last stream reference.
+ */
+ urcu_ref_put(&stream->ref, stream_release);
+ rcu_read_unlock();
+}
+
+int stream_set_pending_rotation(struct relay_stream *stream,
+ struct lttng_trace_chunk *next_trace_chunk,
+ uint64_t rotation_sequence_number)
+{
+ int ret = 0;
+ const struct relay_stream_rotation rotation = {
+ .data_rotated = false,
+ .index_rotated = false,
+ .packet_seq_num = rotation_sequence_number,
+ .prev_data_net_seq = -1ULL,
+ .next_trace_chunk = next_trace_chunk,
+ };
+
+ if (stream->ongoing_rotation.is_set) {
+ ERR("Attempted to set a pending rotation on a stream already being rotated (protocol error)");
+ ret = -1;
+ goto end;
+ }
+
+ if (next_trace_chunk) {
+ const bool reference_acquired =
+ lttng_trace_chunk_get(next_trace_chunk);
+
+ LTTNG_ASSERT(reference_acquired);
+ }
+ LTTNG_OPTIONAL_SET(&stream->ongoing_rotation, rotation);
+
+ DBG("Setting pending rotation: stream_id = %" PRIu64
+ ", rotate_at_packet_seq_num = %" PRIu64,
+ stream->stream_handle, rotation_sequence_number);
+ if (stream->is_metadata) {
+ /*
+ * A metadata stream has no index; consider it already rotated.
+ */
+ stream->ongoing_rotation.value.index_rotated = true;
+ if (next_trace_chunk) {
+ /*
+ * The metadata will be received again in the new chunk.
+ */
+ stream->metadata_received = 0;
+ }
+ ret = stream_rotate_data_file(stream);
+ } else {
+ ret = try_rotate_stream_index(stream);
+ if (ret < 0) {
+ goto end;
+ }
+
+ ret = try_rotate_stream_data(stream);
+ if (ret < 0) {
+ goto end;
+ }
+ }
+end:
+ return ret;
+}
+
+void try_stream_close(struct relay_stream *stream)
+{
+ bool session_aborted;
+ struct relay_session *session = stream->trace->session;
+
+ DBG("Trying to close stream %" PRIu64, stream->stream_handle);
+
+ pthread_mutex_lock(&session->lock);
+ session_aborted = session->aborted;
+ pthread_mutex_unlock(&session->lock);
+
+ pthread_mutex_lock(&stream->lock);
+ /*
+ * Can be called concurently by connection close and reception of last
+ * pending data.
+ */
+ if (stream->closed) {
+ pthread_mutex_unlock(&stream->lock);
+ DBG("closing stream %" PRIu64 " aborted since it is already marked as closed", stream->stream_handle);
+ return;
+ }
+
+ stream->close_requested = true;
+
+ if (stream->last_net_seq_num == -1ULL) {
+ /*
+ * Handle connection close without explicit stream close
+ * command.
+ *
+ * We can be clever about indexes partially received in
+ * cases where we received the data socket part, but not
+ * the control socket part: since we're currently closing
+ * the stream on behalf of the control socket, we *know*
+ * there won't be any more control information for this
+ * socket. Therefore, we can destroy all indexes for
+ * which we have received only the file descriptor (from
+ * data socket). This takes care of consumerd crashes
+ * between sending the data and control information for
+ * a packet. Since those are sent in that order, we take
+ * care of consumerd crashes.
+ */
+ DBG("relay_index_close_partial_fd");
+ relay_index_close_partial_fd(stream);
+ /*
+ * Use the highest net_seq_num we currently have pending
+ * As end of stream indicator. Leave last_net_seq_num
+ * at -1ULL if we cannot find any index.
+ */
+ stream->last_net_seq_num = relay_index_find_last(stream);
+ DBG("Updating stream->last_net_seq_num to %" PRIu64, stream->last_net_seq_num);
+ /* Fall-through into the next check. */
+ }
+
+ if (stream->last_net_seq_num != -1ULL &&
+ ((int64_t) (stream->prev_data_seq - stream->last_net_seq_num)) < 0
+ && !session_aborted) {
+ /*
+ * Don't close since we still have data pending. This
+ * handles cases where an explicit close command has
+ * been received for this stream, and cases where the
+ * connection has been closed, and we are awaiting for
+ * index information from the data socket. It is
+ * therefore expected that all the index fd information
+ * we need has already been received on the control
+ * socket. Matching index information from data socket
+ * should be Expected Soon(TM).
+ *
+ * TODO: We should implement a timer to garbage collect
+ * streams after a timeout to be resilient against a
+ * consumerd implementation that would not match this
+ * expected behavior.
+ */
+ pthread_mutex_unlock(&stream->lock);
+ DBG("closing stream %" PRIu64 " aborted since it still has data pending", stream->stream_handle);
+ return;
+ }
+ /*
+ * We received all the indexes we can expect.
+ */
+ stream_unpublish(stream);
+ stream->closed = true;
+ /* Relay indexes are only used by the "consumer/sessiond" end. */
+ relay_index_close_all(stream);
+
+ /*
+ * If we are closed by an application exiting (per-pid buffers),
+ * we need to put our reference on the stream trace chunk right
+ * away, because otherwise still holding the reference on the
+ * trace chunk could allow a viewer stream (which holds a reference
+ * to the stream) to postpone destroy waiting for the chunk to cease
+ * to exist endlessly until the viewer is detached.
+ */
+
+ /* Put stream fd before put chunk. */
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+ if (stream->index_file) {
+ lttng_index_file_put(stream->index_file);
+ stream->index_file = NULL;
+ }
+ lttng_trace_chunk_put(stream->trace_chunk);
+ stream->trace_chunk = NULL;
+ pthread_mutex_unlock(&stream->lock);
+ DBG("Succeeded in closing stream %" PRIu64, stream->stream_handle);
+ stream_put(stream);
+}
+
+int stream_init_packet(struct relay_stream *stream, size_t packet_size,
+ bool *file_rotated)
+{
+ int ret = 0;
+
+ ASSERT_LOCKED(stream->lock);
+
+ if (!stream->file || !stream->trace_chunk) {
+ ERR("Protocol error: received a packet for a stream that doesn't have a current trace chunk: stream_id = %" PRIu64 ", channel_name = %s",
+ stream->stream_handle, stream->channel_name);
+ ret = -1;
+ goto end;
+ }
+
+ if (caa_likely(stream->tracefile_size == 0)) {
+ /* No size limit set; nothing to check. */
+ goto end;
+ }
+
+ /*
+ * Check if writing the new packet would exceed the maximal file size.
+ */
+ if (caa_unlikely((stream->tracefile_size_current + packet_size) >
+ stream->tracefile_size)) {
+ const uint64_t new_file_index =
+ (stream->tracefile_current_index + 1) %
+ stream->tracefile_count;
+
+ if (new_file_index < stream->tracefile_current_index) {
+ stream->tracefile_wrapped_around = true;
+ }
+ DBG("New stream packet causes stream file rotation: stream_id = %" PRIu64
+ ", current_file_size = %" PRIu64
+ ", packet_size = %zu, current_file_index = %" PRIu64
+ " new_file_index = %" PRIu64,
+ stream->stream_handle,
+ stream->tracefile_size_current, packet_size,
+ stream->tracefile_current_index, new_file_index);
+ tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_WRITE);
+ stream->tracefile_current_index = new_file_index;
+
+ if (stream->file) {
+ fs_handle_close(stream->file);
+ stream->file = NULL;
+ }
+ ret = stream_create_data_output_file_from_trace_chunk(stream,
+ stream->trace_chunk, false, &stream->file);
+ if (ret) {
+ ERR("Failed to perform trace file rotation of stream %" PRIu64,
+ stream->stream_handle);
+ goto end;
+ }
+
+ /*
+ * Reset current size because we just performed a stream
+ * rotation.
+ */
+ DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
+ __func__, stream->stream_handle, stream->tracefile_size_current);
+ stream->tracefile_size_current = 0;
+ *file_rotated = true;
+ } else {
+ *file_rotated = false;
+ }
+end:
+ return ret;
+}
+
+/* Note that the packet is not necessarily complete. */
+int stream_write(struct relay_stream *stream,
+ const struct lttng_buffer_view *packet, size_t padding_len)
+{
+ int ret = 0;
+ ssize_t write_ret;
+ size_t padding_to_write = padding_len;
+ char padding_buffer[FILE_IO_STACK_BUFFER_SIZE];
+
+ ASSERT_LOCKED(stream->lock);
+ memset(padding_buffer, 0,
+ std::min(sizeof(padding_buffer), padding_to_write));
+
+ if (!stream->file || !stream->trace_chunk) {
+ ERR("Protocol error: received a packet for a stream that doesn't have a current trace chunk: stream_id = %" PRIu64 ", channel_name = %s",
+ stream->stream_handle, stream->channel_name);
+ ret = -1;
+ goto end;
+ }
+ if (packet) {
+ write_ret = fs_handle_write(
+ stream->file, packet->data, packet->size);
+ if (write_ret != packet->size) {
+ PERROR("Failed to write to stream file of %sstream %" PRIu64,
+ stream->is_metadata ? "metadata " : "",
+ stream->stream_handle);
+ ret = -1;
+ goto end;
+ }
+ }
+
+ while (padding_to_write > 0) {
+ const size_t padding_to_write_this_pass =
+ std::min(padding_to_write, sizeof(padding_buffer));
+
+ write_ret = fs_handle_write(stream->file, padding_buffer,
+ padding_to_write_this_pass);
+ if (write_ret != padding_to_write_this_pass) {
+ PERROR("Failed to write padding to file of %sstream %" PRIu64,
+ stream->is_metadata ? "metadata " : "",
+ stream->stream_handle);
+ ret = -1;
+ goto end;
+ }
+ padding_to_write -= padding_to_write_this_pass;
+ }
+
+ if (stream->is_metadata) {
+ size_t recv_len;
+
+ recv_len = packet ? packet->size : 0;
+ recv_len += padding_len;
+ stream->metadata_received += recv_len;
+ if (recv_len) {
+ stream->no_new_metadata_notified = false;
+ }
+ }
+
+ DBG("Wrote to %sstream %" PRIu64 ": data_length = %zu, padding_length = %zu",
+ stream->is_metadata ? "metadata " : "",
+ stream->stream_handle,
+ packet ? packet->size : (size_t) 0, padding_len);
+end:
+ return ret;
+}
+
+/*
+ * Update index after receiving a packet for a data stream.
+ *
+ * Called with the stream lock held.
+ *
+ * Return 0 on success else a negative value.
+ */
+int stream_update_index(struct relay_stream *stream, uint64_t net_seq_num,
+ bool rotate_index, bool *flushed, uint64_t total_size)
+{
+ int ret = 0;
+ uint64_t data_offset;
+ struct relay_index *index;
+
+ LTTNG_ASSERT(stream->trace_chunk);
+ ASSERT_LOCKED(stream->lock);
+ /* Get data offset because we are about to update the index. */
+ data_offset = htobe64(stream->tracefile_size_current);
+
+ DBG("handle_index_data: stream %" PRIu64 " net_seq_num %" PRIu64 " data offset %" PRIu64,
+ stream->stream_handle, net_seq_num, stream->tracefile_size_current);
+
+ /*
+ * Lookup for an existing index for that stream id/sequence
+ * number. If it exists, the control thread has already received the
+ * data for it, thus we need to write it to disk.
+ */
+ index = relay_index_get_by_id_or_create(stream, net_seq_num);
+ if (!index) {
+ ret = -1;
+ goto end;
+ }
+
+ if (rotate_index || !stream->index_file) {
+ ret = create_index_file(stream, stream->trace_chunk);
+ if (ret) {
+ ERR("Failed to create index file for stream %" PRIu64,
+ stream->stream_handle);
+ /* Put self-ref for this index due to error. */
+ relay_index_put(index);
+ index = NULL;
+ goto end;
+ }
+ }
+
+ if (relay_index_set_file(index, stream->index_file, data_offset)) {
+ ret = -1;
+ /* Put self-ref for this index due to error. */
+ relay_index_put(index);
+ index = NULL;
+ goto end;
+ }
+
+ ret = relay_index_try_flush(index);
+ if (ret == 0) {
+ tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_READ);
+ tracefile_array_commit_seq(stream->tfa, stream->index_received_seqcount);
+ stream->index_received_seqcount++;
+ LTTNG_OPTIONAL_SET(&stream->received_packet_seq_num,
+ be64toh(index->index_data.packet_seq_num));
+ *flushed = true;
+ } else if (ret > 0) {
+ index->total_size = total_size;
+ /* No flush. */
+ ret = 0;
+ } else {
+ /*
+ * ret < 0
+ *
+ * relay_index_try_flush is responsible for the self-reference
+ * put of the index object on error.
+ */
+ ERR("relay_index_try_flush error %d", ret);
+ ret = -1;
+ }
+end:
+ return ret;
+}
+
+int stream_complete_packet(struct relay_stream *stream, size_t packet_total_size,
+ uint64_t sequence_number, bool index_flushed)
+{
+ int ret = 0;
+
+ ASSERT_LOCKED(stream->lock);
+
+ stream->tracefile_size_current += packet_total_size;
+ if (index_flushed) {
+ stream->pos_after_last_complete_data_index =
+ stream->tracefile_size_current;
+ stream->prev_index_seq = sequence_number;
+ ret = try_rotate_stream_index(stream);
+ if (ret < 0) {
+ goto end;
+ }
+ }
+
+ stream->prev_data_seq = sequence_number;
+ ret = try_rotate_stream_data(stream);
+
+end:
+ return ret;
+}
+
+int stream_add_index(struct relay_stream *stream,
+ const struct lttcomm_relayd_index *index_info)
+{
+ int ret = 0;
+ struct relay_index *index;
+
+ ASSERT_LOCKED(stream->lock);
+
+ DBG("stream_add_index for stream %" PRIu64, stream->stream_handle);
+
+ /* Live beacon handling */
+ if (index_info->packet_size == 0) {
+ DBG("Received live beacon for stream %" PRIu64,
+ stream->stream_handle);
+
+ /*
+ * Only flag a stream inactive when it has already
+ * received data and no indexes are in flight.
+ */
+ if (stream->index_received_seqcount > 0
+ && stream->indexes_in_flight == 0) {
+ stream->beacon_ts_end = index_info->timestamp_end;
+ }
+ ret = 0;
+ goto end;
+ } else {
+ stream->beacon_ts_end = -1ULL;
+ }
+
+ if (stream->ctf_stream_id == -1ULL) {
+ stream->ctf_stream_id = index_info->stream_id;
+ }
+
+ index = relay_index_get_by_id_or_create(stream, index_info->net_seq_num);
+ if (!index) {
+ ret = -1;
+ ERR("Failed to get or create index %" PRIu64,
+ index_info->net_seq_num);
+ goto end;
+ }
+ if (relay_index_set_control_data(index, index_info,
+ stream->trace->session->minor)) {
+ ERR("set_index_control_data error");
+ relay_index_put(index);
+ ret = -1;
+ goto end;
+ }
+ ret = relay_index_try_flush(index);
+ if (ret == 0) {
+ tracefile_array_file_rotate(stream->tfa, TRACEFILE_ROTATE_READ);
+ tracefile_array_commit_seq(stream->tfa, stream->index_received_seqcount);
+ stream->index_received_seqcount++;
+ stream->pos_after_last_complete_data_index += index->total_size;
+ stream->prev_index_seq = index_info->net_seq_num;
+ LTTNG_OPTIONAL_SET(&stream->received_packet_seq_num,
+ index_info->packet_seq_num);
+
+ ret = try_rotate_stream_index(stream);
+ if (ret < 0) {
+ goto end;
+ }
+ ret = try_rotate_stream_data(stream);
+ if (ret < 0) {
+ goto end;
+ }
+ } else if (ret > 0) {
+ /* no flush. */
+ ret = 0;
+ } else {
+ /*
+ * ret < 0
+ *
+ * relay_index_try_flush is responsible for the self-reference
+ * put of the index object on error.
+ */
+ ERR("relay_index_try_flush error %d", ret);
+ ret = -1;
+ }
+end:
+ return ret;
+}
+
+static void print_stream_indexes(struct relay_stream *stream)
+{
+ struct lttng_ht_iter iter;
+ struct relay_index *index;
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(stream->indexes_ht->ht, &iter.iter, index,
+ index_n.node) {
+ DBG("index %p net_seq_num %" PRIu64 " refcount %ld"
+ " stream %" PRIu64 " trace %" PRIu64
+ " session %" PRIu64,
+ index,
+ index->index_n.key,
+ stream->ref.refcount,
+ index->stream->stream_handle,
+ index->stream->trace->id,
+ index->stream->trace->session->id);
+ }
+ rcu_read_unlock();
+}
+
+int stream_reset_file(struct relay_stream *stream)
+{
+ ASSERT_LOCKED(stream->lock);
+
+ if (stream->file) {
+ int ret;
+
+ ret = fs_handle_close(stream->file);
+ if (ret) {
+ ERR("Failed to close stream file handle: channel name = \"%s\", id = %" PRIu64,
+ stream->channel_name,
+ stream->stream_handle);
+ }
+ stream->file = NULL;
+ }
+
+ DBG("%s: reset tracefile_size_current for stream %" PRIu64 " was %" PRIu64,
+ __func__, stream->stream_handle, stream->tracefile_size_current);
+ stream->tracefile_size_current = 0;
+ stream->prev_data_seq = 0;
+ stream->prev_index_seq = 0;
+ /* Note that this does not reset the tracefile array. */
+ stream->tracefile_current_index = 0;
+ stream->pos_after_last_complete_data_index = 0;
+
+ return stream_create_data_output_file_from_trace_chunk(stream,
+ stream->trace_chunk, true, &stream->file);
+}
+
+void print_relay_streams(void)
+{
+ struct lttng_ht_iter iter;
+ struct relay_stream *stream;
+
+ if (!relay_streams_ht) {
+ return;
+ }
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(relay_streams_ht->ht, &iter.iter, stream,
+ node.node) {
+ if (!stream_get(stream)) {
+ continue;
+ }
+ DBG("stream %p refcount %ld stream %" PRIu64 " trace %" PRIu64
+ " session %" PRIu64,
+ stream,
+ stream->ref.refcount,
+ stream->stream_handle,
+ stream->trace->id,
+ stream->trace->session->id);
+ print_stream_indexes(stream);
+ stream_put(stream);
+ }
+ rcu_read_unlock();
+}
+++ /dev/null
-/*
- * Copyright (C) 2017 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#include <sys/types.h>
-#include <netinet/tcp.h>
-#include <stdbool.h>
-#include <sys/socket.h>
-#include <limits.h>
-
-#include <common/compat/getenv.h>
-#include <common/time.h>
-#include <common/defaults.h>
-#include <common/config/session-config.h>
-
-#include "tcp_keep_alive.h"
-
-#define SOLARIS_IDLE_TIME_MIN_S 10
-#define SOLARIS_IDLE_TIME_MAX_S 864000 /* 10 days */
-#define SOLARIS_ABORT_THRESHOLD_MIN_S 1
-#define SOLARIS_ABORT_THRESHOLD_MAX_S 480 /* 8 minutes */
-
-/* Per-platform definitions of TCP socket options. */
-#if defined (__linux__)
-
-#define COMPAT_TCP_LEVEL SOL_TCP
-#define COMPAT_TCP_ABORT_THRESHOLD 0 /* Does not exist on linux. */
-#define COMPAT_TCP_KEEPIDLE TCP_KEEPIDLE
-#define COMPAT_TCP_KEEPINTVL TCP_KEEPINTVL
-#define COMPAT_TCP_KEEPCNT TCP_KEEPCNT
-
-#elif defined (__sun__) /* ! defined (__linux__) */
-
-#define COMPAT_TCP_LEVEL IPPROTO_TCP
-
-#ifdef TCP_KEEPALIVE_THRESHOLD
-#define COMPAT_TCP_KEEPIDLE TCP_KEEPALIVE_THRESHOLD
-#else /* ! defined (TCP_KEEPALIVE_THRESHOLD) */
-#define COMPAT_TCP_KEEPIDLE 0
-#endif /* TCP_KEEPALIVE_THRESHOLD */
-
-#ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
-#define COMPAT_TCP_ABORT_THRESHOLD TCP_KEEPALIVE_ABORT_THRESHOLD
-#else /* ! defined (TCP_KEEPALIVE_ABORT_THRESHOLD) */
-#define COMPAT_TCP_ABORT_THRESHOLD 0
-#endif /* TCP_KEEPALIVE_ABORT_THRESHOLD */
-
-#define COMPAT_TCP_KEEPINTVL 0 /* Does not exist on Solaris. */
-#define COMPAT_TCP_KEEPCNT 0 /* Does not exist on Solaris. */
-
-#else /* ! defined (__linux__) && ! defined (__sun__) */
-
-#define COMPAT_TCP_LEVEL 0
-#define COMPAT_TCP_ABORT_THRESHOLD 0
-#define COMPAT_TCP_KEEPIDLE 0
-#define COMPAT_TCP_KEEPINTVL 0
-#define COMPAT_TCP_KEEPCNT 0
-
-#endif /* ! defined (__linux__) && ! defined (__sun__) */
-
-struct tcp_keep_alive_support {
- /* TCP keep-alive is supported by this platform. */
- bool supported;
- /* Overriding idle-time per socket is supported by this platform. */
- bool idle_time_supported;
- /*
- * Overriding probe interval per socket is supported by this
- * platform.
- */
- bool probe_interval_supported;
- /*
- * Configuring max probe count per socket is supported by this
- * platform.
- */
- bool max_probe_count_supported;
- /* Overriding on a per-socket basis is supported by this platform. */
- bool abort_threshold_supported;
-};
-
-struct tcp_keep_alive_config {
- /* Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV environment variable. */
- bool enabled;
- /*
- * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV environment
- * variable.
- */
- int idle_time;
- /*
- * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
- * environment variable.
- */
- int probe_interval;
- /*
- * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
- * environment variable.
- */
- int max_probe_count;
- /*
- * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
- * environment variable.
- */
- int abort_threshold;
-};
-
-static struct tcp_keep_alive_config the_config = {.enabled = false,
- .idle_time = -1,
- .probe_interval = -1,
- .max_probe_count = -1,
- .abort_threshold = -1};
-
-static struct tcp_keep_alive_support the_support = {.supported = false,
- .idle_time_supported = false,
- .probe_interval_supported = false,
- .max_probe_count_supported = false,
- .abort_threshold_supported = false};
-
-/*
- * Common parser for string to positive int conversion where the value must be
- * in range [-1, INT_MAX].
- *
- * Returns -2 on invalid value.
- */
-static
-int get_env_int(const char *env_var,
- const char *value)
-{
- int ret;
- long tmp;
- char *endptr = NULL;
-
- errno = 0;
- tmp = strtol(value, &endptr, 0);
- if (errno != 0) {
- ERR("%s cannot be parsed.", env_var);
- PERROR("errno for previous parsing failure");
- ret = -2;
- goto end;
- }
-
- if (endptr == value || *endptr != '\0') {
- ERR("%s is not a valid number", env_var);
- ret = -1;
- goto end;
- }
-
- if (tmp < -1) {
- ERR("%s must be greater or equal to -1", env_var);
- ret = -2;
- goto end;
- }
- if (tmp > INT_MAX){
- ERR("%s is too big. Maximum value is %d", env_var, INT_MAX);
- ret = -2;
- goto end;
- }
-
- ret = (int) tmp;
-end:
- return ret;
-}
-
-/*
- * Per-platform implementation of tcp_keep_alive_idle_time_modifier.
- * Returns -2 on invalid value.
- */
-#ifdef __sun__
-
-static
-int convert_idle_time(int value)
-{
- int ret;
- unsigned int tmp_ms;
-
- if (value == -1 || value == 0) {
- /* Use system defaults */
- ret = value;
- goto end;
- }
-
- if (value < 0) {
- ERR("Invalid tcp keep-alive idle time (%i)", value);
- ret = -2;
- goto end;
- }
-
- /*
- * Additional constraints for Solaris 11.
- * Minimum 10s, maximum 10 days. Defined by
- * https://docs.oracle.com/cd/E23824_01/html/821-1475/tcp-7p.html#REFMAN7tcp-7p
- */
- if ((value < SOLARIS_IDLE_TIME_MIN_S ||
- value > SOLARIS_IDLE_TIME_MAX_S)) {
- ERR("%s must be comprised between %d and %d inclusively on Solaris",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
- SOLARIS_IDLE_TIME_MIN_S,
- SOLARIS_IDLE_TIME_MAX_S);
- ret = -2;
- goto end;
- }
-
- /* On Solaris idle time is given in milliseconds. */
- tmp_ms = ((unsigned int) value) * MSEC_PER_SEC;
- if ((value != 0 && (tmp_ms / ((unsigned int) value)) != MSEC_PER_SEC)
- || tmp_ms > INT_MAX) {
- /* Overflow. */
- const int max_value = INT_MAX / MSEC_PER_SEC;
-
- ERR("%s is too big: maximum supported value is %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
- max_value);
- ret = -2;
- goto end;
- }
-
- /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
- ret = (int) tmp_ms;
-end:
- return ret;
-}
-
-#else /* ! defined(__sun__) */
-
-static
-int convert_idle_time(int value)
-{
- return value;
-}
-
-#endif /* ! defined(__sun__) */
-
-/* Per-platform support of tcp_keep_alive functionality. */
-#if defined (__linux__)
-
-static
-void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
-{
- support->supported = true;
- support->idle_time_supported = true;
- support->probe_interval_supported = true;
- support->max_probe_count_supported = true;
- /* Solaris specific */
- support->abort_threshold_supported = false;
-}
-
-#elif defined(__sun__) /* ! defined (__linux__) */
-
-static
-void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
-{
- support->supported = true;
-#ifdef TCP_KEEPALIVE_THRESHOLD
- support->idle_time_supported = true;
-#else
- support->idle_time_supported = false;;
-#endif /* TCP_KEEPALIVE_THRESHOLD */
-
- /*
- * Solaris does not support either tcp_keepalive_probes or
- * tcp_keepalive_intvl.
- * Inferring a value for TCP_KEEP_ALIVE_ABORT_THRESHOLD using
- * (tcp_keepalive_probes * tcp_keepalive_intvl) could yield a good
- * alternative, but Solaris does not detail the algorithm used (such as
- * constant time retry like Linux).
- *
- * Ignore those settings on Solaris 11. We prefer exposing an
- * environment variable only used on Solaris for the abort threshold.
- */
- support->probe_interval_supported = false;
- support->max_probe_count_supported = false;
-#ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
- support->abort_threshold_supported = true;
-#else
- support->abort_threshold_supported = false;
-#endif /* TCP_KEEPALIVE_THRESHOLD */
-}
-
-#else /* ! defined(__sun__) && ! defined(__linux__) */
-
-/* Assume nothing is supported on other platforms. */
-static
-void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
-{
- support->supported = false;
- support->idle_time_supported = false;
- support->probe_interval_supported = false;
- support->max_probe_count_supported = false;
- support->abort_threshold_supported = false;
-}
-
-#endif /* ! defined(__sun__) && ! defined(__linux__) */
-
-#ifdef __sun__
-
-/*
- * Solaris specific modifier for abort threshold.
- * Return -2 on error.
- */
-static
-int convert_abort_threshold(int value)
-{
- int ret;
- unsigned int tmp_ms;
-
- if (value == -1) {
- /* Use system defaults */
- ret = value;
- goto end;
- }
-
- if (value < 0) {
- ERR("Invalid tcp keep-alive abort threshold (%i)", value);
- ret = -2;
- goto end;
- }
-
- /*
- * Additional constraints for Solaris 11.
- *
- * Between 0 and 8 minutes.
- * https://docs.oracle.com/cd/E19120-01/open.solaris/819-2724/fsvdh/index.html
- *
- * Restrict from 1 seconds to 8 minutes sice the 0 value goes against
- * the purpose of dead peers detection by never timing out when probing.
- * It does NOT mean that the connection times out immediately.
- */
- if ((value < SOLARIS_ABORT_THRESHOLD_MIN_S || value > SOLARIS_ABORT_THRESHOLD_MAX_S)) {
- ERR("%s must be comprised between %d and %d inclusively on Solaris",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
- SOLARIS_ABORT_THRESHOLD_MIN_S,
- SOLARIS_ABORT_THRESHOLD_MAX_S);
- ret = -2;
- goto end;
- }
-
- /* Abort threshold is given in milliseconds. */
- tmp_ms = ((unsigned int) value) * MSEC_PER_SEC;
- if ((value != 0 && (tmp_ms / ((unsigned int) value)) != MSEC_PER_SEC)
- || tmp_ms > INT_MAX) {
- /* Overflow */
- const int max_value = INT_MAX / MSEC_PER_SEC;
-
- ERR("%s is too big: maximum supported value is %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
- max_value);
- ret = -2;
- goto end;
- }
-
- /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
- ret = (int) tmp_ms;
-end:
- return ret;
-}
-
-#else
-
-static
-int convert_abort_threshold(int value)
-{
- return value;
-}
-
-#endif /* defined (__sun__) */
-
-/*
- * Retrieve settings from environment variables and warn for settings not
- * supported by the platform.
- */
-static
-int tcp_keep_alive_init_config(struct tcp_keep_alive_support *support,
- struct tcp_keep_alive_config *config)
-{
- int ret;
- const char *value;
-
- value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
- if (!support->supported) {
- if (value) {
- WARN("Using per-socket TCP keep-alive mechanism is not supported by this platform. Ignoring the %s environment variable.",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
- }
- config->enabled = false;
- } else if (value) {
- ret = config_parse_value(value);
- if (ret < 0 || ret > 1) {
- ERR("Invalid value for %s", DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
- ret = 1;
- goto error;
- }
- config->enabled = ret;
- }
- DBG("TCP keep-alive mechanism %s", config->enabled ? "enabled": "disabled");
-
- /* Get value for tcp_keepalive_time in seconds. */
- value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV);
- if (!support->idle_time_supported && value) {
- WARN("Overriding the TCP keep-alive idle time threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV);
- config->idle_time = -1;
- } else if (value) {
- int idle_time_platform;
- int idle_time_seconds;
-
- idle_time_seconds = get_env_int(
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
- value);
- if (idle_time_seconds < -1) {
- ret = 1;
- goto error;
- }
-
- idle_time_platform = convert_idle_time(idle_time_seconds);
- if (idle_time_platform < -1) {
- ret = 1;
- goto error;
- }
-
- config->idle_time = idle_time_platform;
- DBG("Overriding %s to %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
- idle_time_seconds);
- }
-
- /* Get value for tcp_keepalive_intvl in seconds. */
- value = lttng_secure_getenv(
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV);
- if (!support->probe_interval_supported && value) {
- WARN("Overriding the TCP keep-alive probe interval time per-socket is not supported by this platform. Ignoring the %s environment variable.",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV);
- config->probe_interval = -1;
- } else if (value) {
- int probe_interval;
-
- probe_interval = get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV,
- value);
- if (probe_interval < -1) {
- ret = 1;
- goto error;
- }
-
- config->probe_interval = probe_interval;
- DBG("Overriding %s to %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV,
- config->probe_interval);
- }
-
- /* Get value for tcp_keepalive_probes. */
- value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV);
- if (!support->max_probe_count_supported && value) {
- WARN("Overriding the TCP keep-alive maximum probe count per-socket is not supported by this platform. Ignoring the %s environment variable.",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV);
- config->max_probe_count = -1;
- } else if (value) {
- int max_probe_count;
-
- max_probe_count = get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
- value);
- if (max_probe_count < -1) {
- ret = 1;
- goto error;
- }
-
- config->max_probe_count = max_probe_count;
- DBG("Overriding %s to %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
- config->max_probe_count);
- }
-
- /* Get value for tcp_keepalive_abort_interval. */
- value = lttng_secure_getenv(
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV);
- if (!support->abort_threshold_supported && value) {
- WARN("Overriding the TCP keep-alive abort threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV);
- config->abort_threshold = -1;
- } else if (value) {
- int abort_threshold_platform;
- int abort_threshold_seconds;
-
- abort_threshold_seconds = get_env_int(
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
- value);
- if (abort_threshold_seconds < -1) {
- ret = 1;
- goto error;
- }
-
- abort_threshold_platform = convert_abort_threshold(
- abort_threshold_seconds);
- if (abort_threshold_platform < -1) {
- ret = 1;
- goto error;
- }
-
- config->abort_threshold = abort_threshold_platform;
- DBG("Overriding %s to %d",
- DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
- config->abort_threshold);
- }
-
- ret = 0;
-
-error:
- return ret;
-}
-
-/* Initialize the TCP keep-alive configuration. */
-__attribute__((constructor)) static
-void tcp_keep_alive_init(void)
-{
- tcp_keep_alive_init_support(&the_support);
- (void) tcp_keep_alive_init_config(&the_support, &the_config);
-}
-
-/*
- * Set the socket options regarding TCP keep-alive.
- */
-int socket_apply_keep_alive_config(int socket_fd)
-{
- int ret;
- int val = 1;
-
- /* TCP keep-alive */
- if (!the_support.supported || !the_config.enabled) {
- ret = 0;
- goto end;
- }
-
- DBG("TCP keep-alive enabled for socket %d", socket_fd);
- ret = setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, &val,
- sizeof(val));
- if (ret < 0) {
- PERROR("setsockopt so_keepalive");
- goto end;
- }
-
- /* TCP keep-alive idle time */
- if (the_support.idle_time_supported && the_config.idle_time > 0) {
- DBG("TCP keep-alive keep idle: %d enabled for socket %d",
- the_config.idle_time, socket_fd);
- ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
- COMPAT_TCP_KEEPIDLE, &the_config.idle_time,
- sizeof(the_config.idle_time));
- if (ret < 0) {
- PERROR("setsockopt TCP_KEEPIDLE");
- goto end;
- }
- }
- /* TCP keep-alive probe interval */
- if (the_support.probe_interval_supported &&
- the_config.probe_interval > 0) {
- DBG("TCP keep-alive probe_interval: %d enabled for socket %d",
- the_config.probe_interval, socket_fd);
- ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
- COMPAT_TCP_KEEPINTVL,
- &the_config.probe_interval,
- sizeof(the_config.probe_interval));
- if (ret < 0) {
- PERROR("setsockopt TCP_KEEPINTVL");
- goto end;
- }
- }
-
- /* TCP keep-alive max probe count */
- if (the_support.max_probe_count_supported &&
- the_config.max_probe_count > 0) {
- DBG("TCP keep-alive max_probe: %d enabled for socket %d",
- the_config.max_probe_count, socket_fd);
- ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
- COMPAT_TCP_KEEPCNT, &the_config.max_probe_count,
- sizeof(the_config.max_probe_count));
- if (ret < 0) {
- PERROR("setsockopt TCP_KEEPCNT");
- goto end;
- }
- }
-
- /* TCP keep-alive abort threshold */
- if (the_support.abort_threshold_supported &&
- the_config.abort_threshold > 0) {
- DBG("TCP keep-alive abort threshold: %d enabled for socket %d",
- the_config.abort_threshold, socket_fd);
- ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
- COMPAT_TCP_ABORT_THRESHOLD,
- &the_config.abort_threshold,
- sizeof(the_config.max_probe_count));
- if (ret < 0) {
- PERROR("setsockopt TCP_KEEPALIVE_ABORT_THRESHOLD");
- goto end;
- }
- }
-end:
- return ret;
-}
--- /dev/null
+/*
+ * Copyright (C) 2017 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include <sys/types.h>
+#include <netinet/tcp.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <limits.h>
+
+#include <common/compat/getenv.h>
+#include <common/time.h>
+#include <common/defaults.h>
+#include <common/config/session-config.h>
+
+#include "tcp_keep_alive.h"
+
+#define SOLARIS_IDLE_TIME_MIN_S 10
+#define SOLARIS_IDLE_TIME_MAX_S 864000 /* 10 days */
+#define SOLARIS_ABORT_THRESHOLD_MIN_S 1
+#define SOLARIS_ABORT_THRESHOLD_MAX_S 480 /* 8 minutes */
+
+/* Per-platform definitions of TCP socket options. */
+#if defined (__linux__)
+
+#define COMPAT_TCP_LEVEL SOL_TCP
+#define COMPAT_TCP_ABORT_THRESHOLD 0 /* Does not exist on linux. */
+#define COMPAT_TCP_KEEPIDLE TCP_KEEPIDLE
+#define COMPAT_TCP_KEEPINTVL TCP_KEEPINTVL
+#define COMPAT_TCP_KEEPCNT TCP_KEEPCNT
+
+#elif defined (__sun__) /* ! defined (__linux__) */
+
+#define COMPAT_TCP_LEVEL IPPROTO_TCP
+
+#ifdef TCP_KEEPALIVE_THRESHOLD
+#define COMPAT_TCP_KEEPIDLE TCP_KEEPALIVE_THRESHOLD
+#else /* ! defined (TCP_KEEPALIVE_THRESHOLD) */
+#define COMPAT_TCP_KEEPIDLE 0
+#endif /* TCP_KEEPALIVE_THRESHOLD */
+
+#ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
+#define COMPAT_TCP_ABORT_THRESHOLD TCP_KEEPALIVE_ABORT_THRESHOLD
+#else /* ! defined (TCP_KEEPALIVE_ABORT_THRESHOLD) */
+#define COMPAT_TCP_ABORT_THRESHOLD 0
+#endif /* TCP_KEEPALIVE_ABORT_THRESHOLD */
+
+#define COMPAT_TCP_KEEPINTVL 0 /* Does not exist on Solaris. */
+#define COMPAT_TCP_KEEPCNT 0 /* Does not exist on Solaris. */
+
+#else /* ! defined (__linux__) && ! defined (__sun__) */
+
+#define COMPAT_TCP_LEVEL 0
+#define COMPAT_TCP_ABORT_THRESHOLD 0
+#define COMPAT_TCP_KEEPIDLE 0
+#define COMPAT_TCP_KEEPINTVL 0
+#define COMPAT_TCP_KEEPCNT 0
+
+#endif /* ! defined (__linux__) && ! defined (__sun__) */
+
+struct tcp_keep_alive_support {
+ /* TCP keep-alive is supported by this platform. */
+ bool supported;
+ /* Overriding idle-time per socket is supported by this platform. */
+ bool idle_time_supported;
+ /*
+ * Overriding probe interval per socket is supported by this
+ * platform.
+ */
+ bool probe_interval_supported;
+ /*
+ * Configuring max probe count per socket is supported by this
+ * platform.
+ */
+ bool max_probe_count_supported;
+ /* Overriding on a per-socket basis is supported by this platform. */
+ bool abort_threshold_supported;
+};
+
+struct tcp_keep_alive_config {
+ /* Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV environment variable. */
+ bool enabled;
+ /*
+ * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV environment
+ * variable.
+ */
+ int idle_time;
+ /*
+ * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV
+ * environment variable.
+ */
+ int probe_interval;
+ /*
+ * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV
+ * environment variable.
+ */
+ int max_probe_count;
+ /*
+ * Maps to the LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV
+ * environment variable.
+ */
+ int abort_threshold;
+};
+
+static struct tcp_keep_alive_config the_config = {.enabled = false,
+ .idle_time = -1,
+ .probe_interval = -1,
+ .max_probe_count = -1,
+ .abort_threshold = -1};
+
+static struct tcp_keep_alive_support the_support = {.supported = false,
+ .idle_time_supported = false,
+ .probe_interval_supported = false,
+ .max_probe_count_supported = false,
+ .abort_threshold_supported = false};
+
+/*
+ * Common parser for string to positive int conversion where the value must be
+ * in range [-1, INT_MAX].
+ *
+ * Returns -2 on invalid value.
+ */
+static
+int get_env_int(const char *env_var,
+ const char *value)
+{
+ int ret;
+ long tmp;
+ char *endptr = NULL;
+
+ errno = 0;
+ tmp = strtol(value, &endptr, 0);
+ if (errno != 0) {
+ ERR("%s cannot be parsed.", env_var);
+ PERROR("errno for previous parsing failure");
+ ret = -2;
+ goto end;
+ }
+
+ if (endptr == value || *endptr != '\0') {
+ ERR("%s is not a valid number", env_var);
+ ret = -1;
+ goto end;
+ }
+
+ if (tmp < -1) {
+ ERR("%s must be greater or equal to -1", env_var);
+ ret = -2;
+ goto end;
+ }
+ if (tmp > INT_MAX){
+ ERR("%s is too big. Maximum value is %d", env_var, INT_MAX);
+ ret = -2;
+ goto end;
+ }
+
+ ret = (int) tmp;
+end:
+ return ret;
+}
+
+/*
+ * Per-platform implementation of tcp_keep_alive_idle_time_modifier.
+ * Returns -2 on invalid value.
+ */
+#ifdef __sun__
+
+static
+int convert_idle_time(int value)
+{
+ int ret;
+ unsigned int tmp_ms;
+
+ if (value == -1 || value == 0) {
+ /* Use system defaults */
+ ret = value;
+ goto end;
+ }
+
+ if (value < 0) {
+ ERR("Invalid tcp keep-alive idle time (%i)", value);
+ ret = -2;
+ goto end;
+ }
+
+ /*
+ * Additional constraints for Solaris 11.
+ * Minimum 10s, maximum 10 days. Defined by
+ * https://docs.oracle.com/cd/E23824_01/html/821-1475/tcp-7p.html#REFMAN7tcp-7p
+ */
+ if ((value < SOLARIS_IDLE_TIME_MIN_S ||
+ value > SOLARIS_IDLE_TIME_MAX_S)) {
+ ERR("%s must be comprised between %d and %d inclusively on Solaris",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
+ SOLARIS_IDLE_TIME_MIN_S,
+ SOLARIS_IDLE_TIME_MAX_S);
+ ret = -2;
+ goto end;
+ }
+
+ /* On Solaris idle time is given in milliseconds. */
+ tmp_ms = ((unsigned int) value) * MSEC_PER_SEC;
+ if ((value != 0 && (tmp_ms / ((unsigned int) value)) != MSEC_PER_SEC)
+ || tmp_ms > INT_MAX) {
+ /* Overflow. */
+ const int max_value = INT_MAX / MSEC_PER_SEC;
+
+ ERR("%s is too big: maximum supported value is %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
+ max_value);
+ ret = -2;
+ goto end;
+ }
+
+ /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
+ ret = (int) tmp_ms;
+end:
+ return ret;
+}
+
+#else /* ! defined(__sun__) */
+
+static
+int convert_idle_time(int value)
+{
+ return value;
+}
+
+#endif /* ! defined(__sun__) */
+
+/* Per-platform support of tcp_keep_alive functionality. */
+#if defined (__linux__)
+
+static
+void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
+{
+ support->supported = true;
+ support->idle_time_supported = true;
+ support->probe_interval_supported = true;
+ support->max_probe_count_supported = true;
+ /* Solaris specific */
+ support->abort_threshold_supported = false;
+}
+
+#elif defined(__sun__) /* ! defined (__linux__) */
+
+static
+void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
+{
+ support->supported = true;
+#ifdef TCP_KEEPALIVE_THRESHOLD
+ support->idle_time_supported = true;
+#else
+ support->idle_time_supported = false;;
+#endif /* TCP_KEEPALIVE_THRESHOLD */
+
+ /*
+ * Solaris does not support either tcp_keepalive_probes or
+ * tcp_keepalive_intvl.
+ * Inferring a value for TCP_KEEP_ALIVE_ABORT_THRESHOLD using
+ * (tcp_keepalive_probes * tcp_keepalive_intvl) could yield a good
+ * alternative, but Solaris does not detail the algorithm used (such as
+ * constant time retry like Linux).
+ *
+ * Ignore those settings on Solaris 11. We prefer exposing an
+ * environment variable only used on Solaris for the abort threshold.
+ */
+ support->probe_interval_supported = false;
+ support->max_probe_count_supported = false;
+#ifdef TCP_KEEPALIVE_ABORT_THRESHOLD
+ support->abort_threshold_supported = true;
+#else
+ support->abort_threshold_supported = false;
+#endif /* TCP_KEEPALIVE_THRESHOLD */
+}
+
+#else /* ! defined(__sun__) && ! defined(__linux__) */
+
+/* Assume nothing is supported on other platforms. */
+static
+void tcp_keep_alive_init_support(struct tcp_keep_alive_support *support)
+{
+ support->supported = false;
+ support->idle_time_supported = false;
+ support->probe_interval_supported = false;
+ support->max_probe_count_supported = false;
+ support->abort_threshold_supported = false;
+}
+
+#endif /* ! defined(__sun__) && ! defined(__linux__) */
+
+#ifdef __sun__
+
+/*
+ * Solaris specific modifier for abort threshold.
+ * Return -2 on error.
+ */
+static
+int convert_abort_threshold(int value)
+{
+ int ret;
+ unsigned int tmp_ms;
+
+ if (value == -1) {
+ /* Use system defaults */
+ ret = value;
+ goto end;
+ }
+
+ if (value < 0) {
+ ERR("Invalid tcp keep-alive abort threshold (%i)", value);
+ ret = -2;
+ goto end;
+ }
+
+ /*
+ * Additional constraints for Solaris 11.
+ *
+ * Between 0 and 8 minutes.
+ * https://docs.oracle.com/cd/E19120-01/open.solaris/819-2724/fsvdh/index.html
+ *
+ * Restrict from 1 seconds to 8 minutes sice the 0 value goes against
+ * the purpose of dead peers detection by never timing out when probing.
+ * It does NOT mean that the connection times out immediately.
+ */
+ if ((value < SOLARIS_ABORT_THRESHOLD_MIN_S || value > SOLARIS_ABORT_THRESHOLD_MAX_S)) {
+ ERR("%s must be comprised between %d and %d inclusively on Solaris",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
+ SOLARIS_ABORT_THRESHOLD_MIN_S,
+ SOLARIS_ABORT_THRESHOLD_MAX_S);
+ ret = -2;
+ goto end;
+ }
+
+ /* Abort threshold is given in milliseconds. */
+ tmp_ms = ((unsigned int) value) * MSEC_PER_SEC;
+ if ((value != 0 && (tmp_ms / ((unsigned int) value)) != MSEC_PER_SEC)
+ || tmp_ms > INT_MAX) {
+ /* Overflow */
+ const int max_value = INT_MAX / MSEC_PER_SEC;
+
+ ERR("%s is too big: maximum supported value is %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
+ max_value);
+ ret = -2;
+ goto end;
+ }
+
+ /* tmp_ms is >= 0 and <= INT_MAX. Cast is safe. */
+ ret = (int) tmp_ms;
+end:
+ return ret;
+}
+
+#else
+
+static
+int convert_abort_threshold(int value)
+{
+ return value;
+}
+
+#endif /* defined (__sun__) */
+
+/*
+ * Retrieve settings from environment variables and warn for settings not
+ * supported by the platform.
+ */
+static
+int tcp_keep_alive_init_config(struct tcp_keep_alive_support *support,
+ struct tcp_keep_alive_config *config)
+{
+ int ret;
+ const char *value;
+
+ value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
+ if (!support->supported) {
+ if (value) {
+ WARN("Using per-socket TCP keep-alive mechanism is not supported by this platform. Ignoring the %s environment variable.",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
+ }
+ config->enabled = false;
+ } else if (value) {
+ ret = config_parse_value(value);
+ if (ret < 0 || ret > 1) {
+ ERR("Invalid value for %s", DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ENV);
+ ret = 1;
+ goto error;
+ }
+ config->enabled = ret;
+ }
+ DBG("TCP keep-alive mechanism %s", config->enabled ? "enabled": "disabled");
+
+ /* Get value for tcp_keepalive_time in seconds. */
+ value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV);
+ if (!support->idle_time_supported && value) {
+ WARN("Overriding the TCP keep-alive idle time threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV);
+ config->idle_time = -1;
+ } else if (value) {
+ int idle_time_platform;
+ int idle_time_seconds;
+
+ idle_time_seconds = get_env_int(
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
+ value);
+ if (idle_time_seconds < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ idle_time_platform = convert_idle_time(idle_time_seconds);
+ if (idle_time_platform < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ config->idle_time = idle_time_platform;
+ DBG("Overriding %s to %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_IDLE_TIME_ENV,
+ idle_time_seconds);
+ }
+
+ /* Get value for tcp_keepalive_intvl in seconds. */
+ value = lttng_secure_getenv(
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV);
+ if (!support->probe_interval_supported && value) {
+ WARN("Overriding the TCP keep-alive probe interval time per-socket is not supported by this platform. Ignoring the %s environment variable.",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV);
+ config->probe_interval = -1;
+ } else if (value) {
+ int probe_interval;
+
+ probe_interval = get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV,
+ value);
+ if (probe_interval < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ config->probe_interval = probe_interval;
+ DBG("Overriding %s to %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_PROBE_INTERVAL_ENV,
+ config->probe_interval);
+ }
+
+ /* Get value for tcp_keepalive_probes. */
+ value = lttng_secure_getenv(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV);
+ if (!support->max_probe_count_supported && value) {
+ WARN("Overriding the TCP keep-alive maximum probe count per-socket is not supported by this platform. Ignoring the %s environment variable.",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV);
+ config->max_probe_count = -1;
+ } else if (value) {
+ int max_probe_count;
+
+ max_probe_count = get_env_int(DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
+ value);
+ if (max_probe_count < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ config->max_probe_count = max_probe_count;
+ DBG("Overriding %s to %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
+ config->max_probe_count);
+ }
+
+ /* Get value for tcp_keepalive_abort_interval. */
+ value = lttng_secure_getenv(
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV);
+ if (!support->abort_threshold_supported && value) {
+ WARN("Overriding the TCP keep-alive abort threshold per-socket is not supported by this platform. Ignoring the %s environment variable.",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV);
+ config->abort_threshold = -1;
+ } else if (value) {
+ int abort_threshold_platform;
+ int abort_threshold_seconds;
+
+ abort_threshold_seconds = get_env_int(
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_MAX_PROBE_COUNT_ENV,
+ value);
+ if (abort_threshold_seconds < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ abort_threshold_platform = convert_abort_threshold(
+ abort_threshold_seconds);
+ if (abort_threshold_platform < -1) {
+ ret = 1;
+ goto error;
+ }
+
+ config->abort_threshold = abort_threshold_platform;
+ DBG("Overriding %s to %d",
+ DEFAULT_LTTNG_RELAYD_TCP_KEEP_ALIVE_ABORT_THRESHOLD_ENV,
+ config->abort_threshold);
+ }
+
+ ret = 0;
+
+error:
+ return ret;
+}
+
+/* Initialize the TCP keep-alive configuration. */
+__attribute__((constructor)) static
+void tcp_keep_alive_init(void)
+{
+ tcp_keep_alive_init_support(&the_support);
+ (void) tcp_keep_alive_init_config(&the_support, &the_config);
+}
+
+/*
+ * Set the socket options regarding TCP keep-alive.
+ */
+int socket_apply_keep_alive_config(int socket_fd)
+{
+ int ret;
+ int val = 1;
+
+ /* TCP keep-alive */
+ if (!the_support.supported || !the_config.enabled) {
+ ret = 0;
+ goto end;
+ }
+
+ DBG("TCP keep-alive enabled for socket %d", socket_fd);
+ ret = setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, &val,
+ sizeof(val));
+ if (ret < 0) {
+ PERROR("setsockopt so_keepalive");
+ goto end;
+ }
+
+ /* TCP keep-alive idle time */
+ if (the_support.idle_time_supported && the_config.idle_time > 0) {
+ DBG("TCP keep-alive keep idle: %d enabled for socket %d",
+ the_config.idle_time, socket_fd);
+ ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
+ COMPAT_TCP_KEEPIDLE, &the_config.idle_time,
+ sizeof(the_config.idle_time));
+ if (ret < 0) {
+ PERROR("setsockopt TCP_KEEPIDLE");
+ goto end;
+ }
+ }
+ /* TCP keep-alive probe interval */
+ if (the_support.probe_interval_supported &&
+ the_config.probe_interval > 0) {
+ DBG("TCP keep-alive probe_interval: %d enabled for socket %d",
+ the_config.probe_interval, socket_fd);
+ ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
+ COMPAT_TCP_KEEPINTVL,
+ &the_config.probe_interval,
+ sizeof(the_config.probe_interval));
+ if (ret < 0) {
+ PERROR("setsockopt TCP_KEEPINTVL");
+ goto end;
+ }
+ }
+
+ /* TCP keep-alive max probe count */
+ if (the_support.max_probe_count_supported &&
+ the_config.max_probe_count > 0) {
+ DBG("TCP keep-alive max_probe: %d enabled for socket %d",
+ the_config.max_probe_count, socket_fd);
+ ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
+ COMPAT_TCP_KEEPCNT, &the_config.max_probe_count,
+ sizeof(the_config.max_probe_count));
+ if (ret < 0) {
+ PERROR("setsockopt TCP_KEEPCNT");
+ goto end;
+ }
+ }
+
+ /* TCP keep-alive abort threshold */
+ if (the_support.abort_threshold_supported &&
+ the_config.abort_threshold > 0) {
+ DBG("TCP keep-alive abort threshold: %d enabled for socket %d",
+ the_config.abort_threshold, socket_fd);
+ ret = setsockopt(socket_fd, COMPAT_TCP_LEVEL,
+ COMPAT_TCP_ABORT_THRESHOLD,
+ &the_config.abort_threshold,
+ sizeof(the_config.max_probe_count));
+ if (ret < 0) {
+ PERROR("setsockopt TCP_KEEPALIVE_ABORT_THRESHOLD");
+ goto end;
+ }
+ }
+end:
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <common/utils.h>
-#include <common/defaults.h>
-
-#include "tracefile-array.h"
-
-struct tracefile_array *tracefile_array_create(size_t count)
-{
- struct tracefile_array *tfa = NULL;
- int i;
-
- tfa = zmalloc(sizeof(*tfa));
- if (!tfa) {
- goto error;
- }
- tfa->tf = zmalloc(sizeof(*tfa->tf) * count);
- if (!tfa->tf) {
- goto error;
- }
- tfa->count = count;
- for (i = 0; i < count; i++) {
- tfa->tf[i].seq_head = -1ULL;
- tfa->tf[i].seq_tail = -1ULL;
- }
- tfa->seq_head = -1ULL;
- tfa->seq_tail = -1ULL;
- return tfa;
-
-error:
- if (tfa) {
- free(tfa->tf);
- }
- free(tfa);
- return NULL;
-}
-
-void tracefile_array_destroy(struct tracefile_array *tfa)
-{
- if (!tfa) {
- return;
- }
- free(tfa->tf);
- free(tfa);
-}
-
-void tracefile_array_reset(struct tracefile_array *tfa)
-{
- size_t count, i;
-
- count = tfa->count;
- for (i = 0; i < count; i++) {
- tfa->tf[i].seq_head = -1ULL;
- tfa->tf[i].seq_tail = -1ULL;
- }
- tfa->seq_head = -1ULL;
- tfa->seq_tail = -1ULL;
- tfa->file_head_read = 0;
- tfa->file_head_write = 0;
- tfa->file_tail = 0;
-}
-
-void tracefile_array_file_rotate(struct tracefile_array *tfa,
- enum tracefile_rotate_type type)
-{
- uint64_t *headp, *tailp;
-
- if (!tfa->count) {
- /* Not in tracefile rotation mode. */
- return;
- }
- switch (type) {
- case TRACEFILE_ROTATE_READ:
- /*
- * Rotate read head to write head position, thus allowing
- * reader to consume the newly rotated head file.
- */
- tfa->file_head_read = tfa->file_head_write;
- break;
- case TRACEFILE_ROTATE_WRITE:
- /* Rotate write head to next file, pushing tail if needed. */
- tfa->file_head_write = (tfa->file_head_write + 1) % tfa->count;
- if (tfa->file_head_write == tfa->file_tail) {
- /* Move tail. */
- tfa->file_tail = (tfa->file_tail + 1) % tfa->count;
- }
- headp = &tfa->tf[tfa->file_head_write].seq_head;
- tailp = &tfa->tf[tfa->file_head_write].seq_tail;
- /*
- * If we overwrite a file with content, we need to push the tail
- * to the position following the content we are overwriting.
- */
- if (*headp != -1ULL) {
- tfa->seq_tail = tfa->tf[tfa->file_tail].seq_tail;
- }
- /* Reset this file head/tail (overwrite). */
- *headp = -1ULL;
- *tailp = -1ULL;
- break;
- default:
- abort();
- }
-}
-
-void tracefile_array_commit_seq(struct tracefile_array *tfa,
- uint64_t new_seq_head)
-{
- uint64_t *headp, *tailp;
-
- /* Increment overall head. */
- tfa->seq_head = new_seq_head;
- /* If we are committing our first index overall, set tail to head. */
- if (tfa->seq_tail == -1ULL) {
- tfa->seq_tail = new_seq_head;
- }
- if (!tfa->count) {
- /* Not in tracefile rotation mode. */
- return;
- }
- headp = &tfa->tf[tfa->file_head_write].seq_head;
- tailp = &tfa->tf[tfa->file_head_write].seq_tail;
- /* Update head tracefile seq_head. */
- *headp = tfa->seq_head;
- /*
- * If we are committing our first index in this packet, set tail
- * to this index seq count.
- */
- if (*tailp == -1ULL) {
- *tailp = tfa->seq_head;
- }
-}
-
-uint64_t tracefile_array_get_read_file_index_head(struct tracefile_array *tfa)
-{
- return tfa->file_head_read;
-}
-
-uint64_t tracefile_array_get_seq_head(struct tracefile_array *tfa)
-{
- return tfa->seq_head;
-}
-
-uint64_t tracefile_array_get_file_index_tail(struct tracefile_array *tfa)
-{
- return tfa->file_tail;
-}
-
-uint64_t tracefile_array_get_seq_tail(struct tracefile_array *tfa)
-{
- return tfa->seq_tail;
-}
-
-bool tracefile_array_seq_in_file(struct tracefile_array *tfa,
- uint64_t file_index, uint64_t seq)
-{
- if (!tfa->count) {
- /*
- * Not in tracefile rotation mode; we are guaranteed to have the
- * index in this file.
- */
- return true;
- }
- LTTNG_ASSERT(file_index < tfa->count);
- if (seq == -1ULL) {
- return false;
- }
- if (seq >= tfa->tf[file_index].seq_tail
- && seq <= tfa->tf[file_index].seq_head) {
- return true;
- } else {
- return false;
- }
-}
--- /dev/null
+/*
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <common/common.h>
+#include <common/utils.h>
+#include <common/defaults.h>
+
+#include "tracefile-array.h"
+
+struct tracefile_array *tracefile_array_create(size_t count)
+{
+ struct tracefile_array *tfa = NULL;
+ int i;
+
+ tfa = (tracefile_array *) zmalloc(sizeof(*tfa));
+ if (!tfa) {
+ goto error;
+ }
+ tfa->tf = (tracefile *) zmalloc(sizeof(*tfa->tf) * count);
+ if (!tfa->tf) {
+ goto error;
+ }
+ tfa->count = count;
+ for (i = 0; i < count; i++) {
+ tfa->tf[i].seq_head = -1ULL;
+ tfa->tf[i].seq_tail = -1ULL;
+ }
+ tfa->seq_head = -1ULL;
+ tfa->seq_tail = -1ULL;
+ return tfa;
+
+error:
+ if (tfa) {
+ free(tfa->tf);
+ }
+ free(tfa);
+ return NULL;
+}
+
+void tracefile_array_destroy(struct tracefile_array *tfa)
+{
+ if (!tfa) {
+ return;
+ }
+ free(tfa->tf);
+ free(tfa);
+}
+
+void tracefile_array_reset(struct tracefile_array *tfa)
+{
+ size_t count, i;
+
+ count = tfa->count;
+ for (i = 0; i < count; i++) {
+ tfa->tf[i].seq_head = -1ULL;
+ tfa->tf[i].seq_tail = -1ULL;
+ }
+ tfa->seq_head = -1ULL;
+ tfa->seq_tail = -1ULL;
+ tfa->file_head_read = 0;
+ tfa->file_head_write = 0;
+ tfa->file_tail = 0;
+}
+
+void tracefile_array_file_rotate(struct tracefile_array *tfa,
+ enum tracefile_rotate_type type)
+{
+ uint64_t *headp, *tailp;
+
+ if (!tfa->count) {
+ /* Not in tracefile rotation mode. */
+ return;
+ }
+ switch (type) {
+ case TRACEFILE_ROTATE_READ:
+ /*
+ * Rotate read head to write head position, thus allowing
+ * reader to consume the newly rotated head file.
+ */
+ tfa->file_head_read = tfa->file_head_write;
+ break;
+ case TRACEFILE_ROTATE_WRITE:
+ /* Rotate write head to next file, pushing tail if needed. */
+ tfa->file_head_write = (tfa->file_head_write + 1) % tfa->count;
+ if (tfa->file_head_write == tfa->file_tail) {
+ /* Move tail. */
+ tfa->file_tail = (tfa->file_tail + 1) % tfa->count;
+ }
+ headp = &tfa->tf[tfa->file_head_write].seq_head;
+ tailp = &tfa->tf[tfa->file_head_write].seq_tail;
+ /*
+ * If we overwrite a file with content, we need to push the tail
+ * to the position following the content we are overwriting.
+ */
+ if (*headp != -1ULL) {
+ tfa->seq_tail = tfa->tf[tfa->file_tail].seq_tail;
+ }
+ /* Reset this file head/tail (overwrite). */
+ *headp = -1ULL;
+ *tailp = -1ULL;
+ break;
+ default:
+ abort();
+ }
+}
+
+void tracefile_array_commit_seq(struct tracefile_array *tfa,
+ uint64_t new_seq_head)
+{
+ uint64_t *headp, *tailp;
+
+ /* Increment overall head. */
+ tfa->seq_head = new_seq_head;
+ /* If we are committing our first index overall, set tail to head. */
+ if (tfa->seq_tail == -1ULL) {
+ tfa->seq_tail = new_seq_head;
+ }
+ if (!tfa->count) {
+ /* Not in tracefile rotation mode. */
+ return;
+ }
+ headp = &tfa->tf[tfa->file_head_write].seq_head;
+ tailp = &tfa->tf[tfa->file_head_write].seq_tail;
+ /* Update head tracefile seq_head. */
+ *headp = tfa->seq_head;
+ /*
+ * If we are committing our first index in this packet, set tail
+ * to this index seq count.
+ */
+ if (*tailp == -1ULL) {
+ *tailp = tfa->seq_head;
+ }
+}
+
+uint64_t tracefile_array_get_read_file_index_head(struct tracefile_array *tfa)
+{
+ return tfa->file_head_read;
+}
+
+uint64_t tracefile_array_get_seq_head(struct tracefile_array *tfa)
+{
+ return tfa->seq_head;
+}
+
+uint64_t tracefile_array_get_file_index_tail(struct tracefile_array *tfa)
+{
+ return tfa->file_tail;
+}
+
+uint64_t tracefile_array_get_seq_tail(struct tracefile_array *tfa)
+{
+ return tfa->seq_tail;
+}
+
+bool tracefile_array_seq_in_file(struct tracefile_array *tfa,
+ uint64_t file_index, uint64_t seq)
+{
+ if (!tfa->count) {
+ /*
+ * Not in tracefile rotation mode; we are guaranteed to have the
+ * index in this file.
+ */
+ return true;
+ }
+ LTTNG_ASSERT(file_index < tfa->count);
+ if (seq == -1ULL) {
+ return false;
+ }
+ if (seq >= tfa->tf[file_index].seq_tail
+ && seq <= tfa->tf[file_index].seq_head) {
+ return true;
+ } else {
+ return false;
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <common/common.h>
-#include <common/defaults.h>
-#include <common/utils.h>
-
-#include "lttng-relayd.h"
-#include "utils.h"
-
-static char *create_output_path_auto(const char *path_name)
-{
- int ret;
- char *traces_path = NULL;
- const char *default_path;
-
- default_path = utils_get_home_dir();
- if (default_path == NULL) {
- ERR("Home path not found.\n \
- Please specify an output path using -o, --output PATH");
- goto exit;
- }
- ret = asprintf(&traces_path, "%s/" DEFAULT_TRACE_DIR_NAME
- "/%s", default_path, path_name);
- if (ret < 0) {
- PERROR("asprintf trace dir name");
- goto exit;
- }
-exit:
- return traces_path;
-}
-
-static char *create_output_path_noauto(const char *path_name)
-{
- int ret;
- char *traces_path = NULL;
- char *full_path;
-
- full_path = utils_expand_path(opt_output_path);
- if (!full_path) {
- goto exit;
- }
-
- ret = asprintf(&traces_path, "%s/%s", full_path, path_name);
- if (ret < 0) {
- PERROR("asprintf trace dir name");
- goto exit;
- }
-exit:
- free(full_path);
- return traces_path;
-}
-
-/*
- * Create the output trace directory path name string.
- *
- * Return the allocated string containing the path name or else NULL.
- */
-char *create_output_path(const char *path_name)
-{
- LTTNG_ASSERT(path_name);
-
- if (opt_output_path == NULL) {
- return create_output_path_auto(path_name);
- } else {
- return create_output_path_noauto(path_name);
- }
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <common/common.h>
+#include <common/defaults.h>
+#include <common/utils.h>
+
+#include "lttng-relayd.h"
+#include "utils.h"
+
+static char *create_output_path_auto(const char *path_name)
+{
+ int ret;
+ char *traces_path = NULL;
+ const char *default_path;
+
+ default_path = utils_get_home_dir();
+ if (default_path == NULL) {
+ ERR("Home path not found.\n \
+ Please specify an output path using -o, --output PATH");
+ goto exit;
+ }
+ ret = asprintf(&traces_path, "%s/" DEFAULT_TRACE_DIR_NAME
+ "/%s", default_path, path_name);
+ if (ret < 0) {
+ PERROR("asprintf trace dir name");
+ goto exit;
+ }
+exit:
+ return traces_path;
+}
+
+static char *create_output_path_noauto(const char *path_name)
+{
+ int ret;
+ char *traces_path = NULL;
+ char *full_path;
+
+ full_path = utils_expand_path(opt_output_path);
+ if (!full_path) {
+ goto exit;
+ }
+
+ ret = asprintf(&traces_path, "%s/%s", full_path, path_name);
+ if (ret < 0) {
+ PERROR("asprintf trace dir name");
+ goto exit;
+ }
+exit:
+ free(full_path);
+ return traces_path;
+}
+
+/*
+ * Create the output trace directory path name string.
+ *
+ * Return the allocated string containing the path name or else NULL.
+ */
+char *create_output_path(const char *path_name)
+{
+ LTTNG_ASSERT(path_name);
+
+ if (opt_output_path == NULL) {
+ return create_output_path_auto(path_name);
+ } else {
+ return create_output_path_noauto(path_name);
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <urcu/rculist.h>
-
-#include "lttng-relayd.h"
-#include "ctf-trace.h"
-#include "session.h"
-#include "viewer-session.h"
-#include "viewer-stream.h"
-#include "stream.h"
-
-struct relay_viewer_session *viewer_session_create(void)
-{
- struct relay_viewer_session *vsession;
-
- vsession = zmalloc(sizeof(*vsession));
- if (!vsession) {
- goto end;
- }
- CDS_INIT_LIST_HEAD(&vsession->session_list);
-end:
- return vsession;
-}
-
-int viewer_session_set_trace_chunk_copy(struct relay_viewer_session *vsession,
- struct lttng_trace_chunk *relay_session_trace_chunk)
-{
- int ret = 0;
- struct lttng_trace_chunk *viewer_chunk;
-
- lttng_trace_chunk_put(vsession->current_trace_chunk);
- vsession->current_trace_chunk = NULL;
-
- DBG("Copying relay session's current trace chunk to the viewer session");
- if (!relay_session_trace_chunk) {
- goto end;
- }
-
- viewer_chunk = lttng_trace_chunk_copy(relay_session_trace_chunk);
- if (!viewer_chunk) {
- ERR("Failed to create a viewer trace chunk from the relay session's current chunk");
- ret = -1;
- goto end;
- }
-
- vsession->current_trace_chunk = viewer_chunk;
-end:
- return ret;
-}
-
-/* The existence of session must be guaranteed by the caller. */
-enum lttng_viewer_attach_return_code viewer_session_attach(
- struct relay_viewer_session *vsession,
- struct relay_session *session)
-{
- enum lttng_viewer_attach_return_code viewer_attach_status =
- LTTNG_VIEWER_ATTACH_OK;
-
- ASSERT_LOCKED(session->lock);
-
- /* Will not fail, as per the ownership guarantee. */
- if (!session_get(session)) {
- viewer_attach_status = LTTNG_VIEWER_ATTACH_UNK;
- goto end;
- }
- if (session->viewer_attached) {
- viewer_attach_status = LTTNG_VIEWER_ATTACH_ALREADY;
- } else {
- int ret;
-
- LTTNG_ASSERT(!vsession->current_trace_chunk);
- session->viewer_attached = true;
-
- ret = viewer_session_set_trace_chunk_copy(vsession,
- session->current_trace_chunk);
- if (ret) {
- /*
- * The live protocol does not define a generic error
- * value for the "attach" command. The "unknown"
- * status is used so that the viewer may handle this
- * failure as if the session didn't exist anymore.
- */
- DBG("Failed to create a viewer trace chunk from the current trace chunk of session \"%s\", returning LTTNG_VIEWER_ATTACH_UNK",
- session->session_name);
- viewer_attach_status = LTTNG_VIEWER_ATTACH_UNK;
- }
- }
-
- if (viewer_attach_status == LTTNG_VIEWER_ATTACH_OK) {
- pthread_mutex_lock(&vsession->session_list_lock);
- /* Ownership is transfered to the list. */
- cds_list_add_rcu(&session->viewer_session_node,
- &vsession->session_list);
- pthread_mutex_unlock(&vsession->session_list_lock);
- } else {
- /* Put our local ref. */
- session_put(session);
- }
-end:
- return viewer_attach_status;
-}
-
-/* The existence of session must be guaranteed by the caller. */
-static int viewer_session_detach(struct relay_viewer_session *vsession,
- struct relay_session *session)
-{
- int ret = 0;
-
- pthread_mutex_lock(&session->lock);
- if (!session->viewer_attached) {
- ret = -1;
- } else {
- session->viewer_attached = false;
- }
-
- if (!ret) {
- pthread_mutex_lock(&vsession->session_list_lock);
- cds_list_del_rcu(&session->viewer_session_node);
- pthread_mutex_unlock(&vsession->session_list_lock);
- /* Release reference held by the list. */
- session_put(session);
- }
- /* Safe since we know the session exists. */
- pthread_mutex_unlock(&session->lock);
- return ret;
-}
-
-void viewer_session_destroy(struct relay_viewer_session *vsession)
-{
- lttng_trace_chunk_put(vsession->current_trace_chunk);
- free(vsession);
-}
-
-/*
- * Release ownership of all the streams of one session and detach the viewer.
- */
-void viewer_session_close_one_session(struct relay_viewer_session *vsession,
- struct relay_session *session)
-{
- struct lttng_ht_iter iter;
- struct relay_viewer_stream *vstream;
-
- /*
- * TODO: improvement: create more efficient list of
- * vstream per session.
- */
- cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter,
- vstream, stream_n.node) {
- if (!viewer_stream_get(vstream)) {
- continue;
- }
- if (vstream->stream->trace->session != session) {
- viewer_stream_put(vstream);
- continue;
- }
- /* Put local reference. */
- viewer_stream_put(vstream);
- /*
- * We have reached one of the viewer stream's lifetime
- * end condition. This "put" will cause the proper
- * teardown of the viewer stream.
- */
- viewer_stream_put(vstream);
- }
- lttng_trace_chunk_put(vsession->current_trace_chunk);
- vsession->current_trace_chunk = NULL;
- viewer_session_detach(vsession, session);
-}
-
-void viewer_session_close(struct relay_viewer_session *vsession)
-{
- struct relay_session *session;
-
- rcu_read_lock();
- cds_list_for_each_entry_rcu(session,
- &vsession->session_list, viewer_session_node) {
- viewer_session_close_one_session(vsession, session);
- }
- rcu_read_unlock();
-}
-
-/*
- * Check if a connection is attached to a session.
- * Return 1 if attached, 0 if not attached, a negative value on error.
- */
-int viewer_session_is_attached(struct relay_viewer_session *vsession,
- struct relay_session *session)
-{
- struct relay_session *iter;
- int found = 0;
-
- pthread_mutex_lock(&session->lock);
- if (!vsession) {
- goto end;
- }
- if (!session->viewer_attached) {
- goto end;
- }
- rcu_read_lock();
- cds_list_for_each_entry_rcu(iter,
- &vsession->session_list,
- viewer_session_node) {
- if (session == iter) {
- found = 1;
- goto end_rcu_unlock;
- }
- }
-end_rcu_unlock:
- rcu_read_unlock();
-end:
- pthread_mutex_unlock(&session->lock);
- return found;
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <common/common.h>
+#include <urcu/rculist.h>
+
+#include "lttng-relayd.h"
+#include "ctf-trace.h"
+#include "session.h"
+#include "viewer-session.h"
+#include "viewer-stream.h"
+#include "stream.h"
+
+struct relay_viewer_session *viewer_session_create(void)
+{
+ struct relay_viewer_session *vsession;
+
+ vsession = (relay_viewer_session *) zmalloc(sizeof(*vsession));
+ if (!vsession) {
+ goto end;
+ }
+ CDS_INIT_LIST_HEAD(&vsession->session_list);
+end:
+ return vsession;
+}
+
+int viewer_session_set_trace_chunk_copy(struct relay_viewer_session *vsession,
+ struct lttng_trace_chunk *relay_session_trace_chunk)
+{
+ int ret = 0;
+ struct lttng_trace_chunk *viewer_chunk;
+
+ lttng_trace_chunk_put(vsession->current_trace_chunk);
+ vsession->current_trace_chunk = NULL;
+
+ DBG("Copying relay session's current trace chunk to the viewer session");
+ if (!relay_session_trace_chunk) {
+ goto end;
+ }
+
+ viewer_chunk = lttng_trace_chunk_copy(relay_session_trace_chunk);
+ if (!viewer_chunk) {
+ ERR("Failed to create a viewer trace chunk from the relay session's current chunk");
+ ret = -1;
+ goto end;
+ }
+
+ vsession->current_trace_chunk = viewer_chunk;
+end:
+ return ret;
+}
+
+/* The existence of session must be guaranteed by the caller. */
+enum lttng_viewer_attach_return_code viewer_session_attach(
+ struct relay_viewer_session *vsession,
+ struct relay_session *session)
+{
+ enum lttng_viewer_attach_return_code viewer_attach_status =
+ LTTNG_VIEWER_ATTACH_OK;
+
+ ASSERT_LOCKED(session->lock);
+
+ /* Will not fail, as per the ownership guarantee. */
+ if (!session_get(session)) {
+ viewer_attach_status = LTTNG_VIEWER_ATTACH_UNK;
+ goto end;
+ }
+ if (session->viewer_attached) {
+ viewer_attach_status = LTTNG_VIEWER_ATTACH_ALREADY;
+ } else {
+ int ret;
+
+ LTTNG_ASSERT(!vsession->current_trace_chunk);
+ session->viewer_attached = true;
+
+ ret = viewer_session_set_trace_chunk_copy(vsession,
+ session->current_trace_chunk);
+ if (ret) {
+ /*
+ * The live protocol does not define a generic error
+ * value for the "attach" command. The "unknown"
+ * status is used so that the viewer may handle this
+ * failure as if the session didn't exist anymore.
+ */
+ DBG("Failed to create a viewer trace chunk from the current trace chunk of session \"%s\", returning LTTNG_VIEWER_ATTACH_UNK",
+ session->session_name);
+ viewer_attach_status = LTTNG_VIEWER_ATTACH_UNK;
+ }
+ }
+
+ if (viewer_attach_status == LTTNG_VIEWER_ATTACH_OK) {
+ pthread_mutex_lock(&vsession->session_list_lock);
+ /* Ownership is transfered to the list. */
+ cds_list_add_rcu(&session->viewer_session_node,
+ &vsession->session_list);
+ pthread_mutex_unlock(&vsession->session_list_lock);
+ } else {
+ /* Put our local ref. */
+ session_put(session);
+ }
+end:
+ return viewer_attach_status;
+}
+
+/* The existence of session must be guaranteed by the caller. */
+static int viewer_session_detach(struct relay_viewer_session *vsession,
+ struct relay_session *session)
+{
+ int ret = 0;
+
+ pthread_mutex_lock(&session->lock);
+ if (!session->viewer_attached) {
+ ret = -1;
+ } else {
+ session->viewer_attached = false;
+ }
+
+ if (!ret) {
+ pthread_mutex_lock(&vsession->session_list_lock);
+ cds_list_del_rcu(&session->viewer_session_node);
+ pthread_mutex_unlock(&vsession->session_list_lock);
+ /* Release reference held by the list. */
+ session_put(session);
+ }
+ /* Safe since we know the session exists. */
+ pthread_mutex_unlock(&session->lock);
+ return ret;
+}
+
+void viewer_session_destroy(struct relay_viewer_session *vsession)
+{
+ lttng_trace_chunk_put(vsession->current_trace_chunk);
+ free(vsession);
+}
+
+/*
+ * Release ownership of all the streams of one session and detach the viewer.
+ */
+void viewer_session_close_one_session(struct relay_viewer_session *vsession,
+ struct relay_session *session)
+{
+ struct lttng_ht_iter iter;
+ struct relay_viewer_stream *vstream;
+
+ /*
+ * TODO: improvement: create more efficient list of
+ * vstream per session.
+ */
+ cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter,
+ vstream, stream_n.node) {
+ if (!viewer_stream_get(vstream)) {
+ continue;
+ }
+ if (vstream->stream->trace->session != session) {
+ viewer_stream_put(vstream);
+ continue;
+ }
+ /* Put local reference. */
+ viewer_stream_put(vstream);
+ /*
+ * We have reached one of the viewer stream's lifetime
+ * end condition. This "put" will cause the proper
+ * teardown of the viewer stream.
+ */
+ viewer_stream_put(vstream);
+ }
+ lttng_trace_chunk_put(vsession->current_trace_chunk);
+ vsession->current_trace_chunk = NULL;
+ viewer_session_detach(vsession, session);
+}
+
+void viewer_session_close(struct relay_viewer_session *vsession)
+{
+ struct relay_session *session;
+
+ rcu_read_lock();
+ cds_list_for_each_entry_rcu(session,
+ &vsession->session_list, viewer_session_node) {
+ viewer_session_close_one_session(vsession, session);
+ }
+ rcu_read_unlock();
+}
+
+/*
+ * Check if a connection is attached to a session.
+ * Return 1 if attached, 0 if not attached, a negative value on error.
+ */
+int viewer_session_is_attached(struct relay_viewer_session *vsession,
+ struct relay_session *session)
+{
+ struct relay_session *iter;
+ int found = 0;
+
+ pthread_mutex_lock(&session->lock);
+ if (!vsession) {
+ goto end;
+ }
+ if (!session->viewer_attached) {
+ goto end;
+ }
+ rcu_read_lock();
+ cds_list_for_each_entry_rcu(iter,
+ &vsession->session_list,
+ viewer_session_node) {
+ if (session == iter) {
+ found = 1;
+ goto end_rcu_unlock;
+ }
+ }
+end_rcu_unlock:
+ rcu_read_unlock();
+end:
+ pthread_mutex_unlock(&session->lock);
+ return found;
+}
#include <common/trace-chunk.h>
#include "session.h"
+#include "lttng-viewer-abi.h"
struct relay_viewer_session {
/*
+++ /dev/null
-/*
- * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#define _LGPL_SOURCE
-#include <common/common.h>
-#include <common/index/index.h>
-#include <common/compat/string.h>
-#include <common/utils.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "lttng-relayd.h"
-#include "viewer-stream.h"
-
-static void viewer_stream_destroy(struct relay_viewer_stream *vstream)
-{
- lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
- free(vstream->path_name);
- free(vstream->channel_name);
- free(vstream);
-}
-
-static void viewer_stream_destroy_rcu(struct rcu_head *head)
-{
- struct relay_viewer_stream *vstream =
- caa_container_of(head, struct relay_viewer_stream, rcu_node);
-
- viewer_stream_destroy(vstream);
-}
-
-/* Relay stream's lock must be held by the caller. */
-struct relay_viewer_stream *viewer_stream_create(struct relay_stream *stream,
- struct lttng_trace_chunk *trace_chunk,
- enum lttng_viewer_seek seek_t)
-{
- struct relay_viewer_stream *vstream = NULL;
-
- ASSERT_LOCKED(stream->lock);
-
- vstream = zmalloc(sizeof(*vstream));
- if (!vstream) {
- PERROR("relay viewer stream zmalloc");
- goto error;
- }
-
- if (trace_chunk) {
- const bool acquired_reference = lttng_trace_chunk_get(
- trace_chunk);
-
- LTTNG_ASSERT(acquired_reference);
- }
-
- vstream->stream_file.trace_chunk = trace_chunk;
- vstream->path_name = lttng_strndup(stream->path_name, LTTNG_VIEWER_PATH_MAX);
- if (vstream->path_name == NULL) {
- PERROR("relay viewer path_name alloc");
- goto error;
- }
- vstream->channel_name = lttng_strndup(stream->channel_name,
- LTTNG_VIEWER_NAME_MAX);
- if (vstream->channel_name == NULL) {
- PERROR("relay viewer channel_name alloc");
- goto error;
- }
-
- if (!stream_get(stream)) {
- ERR("Cannot get stream");
- goto error;
- }
- vstream->stream = stream;
-
- if (stream->is_metadata && stream->trace->viewer_metadata_stream) {
- ERR("Cannot attach viewer metadata stream to trace (busy).");
- goto error;
- }
-
- switch (seek_t) {
- case LTTNG_VIEWER_SEEK_BEGINNING:
- {
- uint64_t seq_tail = tracefile_array_get_seq_tail(stream->tfa);
-
- if (seq_tail == -1ULL) {
- /*
- * Tail may not be initialized yet. Nonetheless, we know
- * we want to send the first index once it becomes
- * available.
- */
- seq_tail = 0;
- }
- vstream->current_tracefile_id =
- tracefile_array_get_file_index_tail(stream->tfa);
- vstream->index_sent_seqcount = seq_tail;
- break;
- }
- case LTTNG_VIEWER_SEEK_LAST:
- vstream->current_tracefile_id =
- tracefile_array_get_read_file_index_head(stream->tfa);
- /*
- * We seek at the very end of each stream, awaiting for
- * a future packet to eventually come in.
- *
- * We don't need to check the head position for -1ULL since the
- * increment will set it to 0.
- */
- vstream->index_sent_seqcount =
- tracefile_array_get_seq_head(stream->tfa) + 1;
- break;
- default:
- goto error;
- }
-
- /*
- * If we never received an index for the current stream, delay
- * the opening of the index, otherwise open it right now.
- */
- if (stream->index_file == NULL) {
- vstream->index_file = NULL;
- } else if (vstream->stream_file.trace_chunk) {
- const uint32_t connection_major = stream->trace->session->major;
- const uint32_t connection_minor = stream->trace->session->minor;
- enum lttng_trace_chunk_status chunk_status;
-
- chunk_status = lttng_index_file_create_from_trace_chunk_read_only(
- vstream->stream_file.trace_chunk,
- stream->path_name,
- stream->channel_name, stream->tracefile_size,
- vstream->current_tracefile_id,
- lttng_to_index_major(connection_major,
- connection_minor),
- lttng_to_index_minor(connection_major,
- connection_minor),
- true, &vstream->index_file);
- if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
- vstream->index_file = NULL;
- } else {
- goto error;
- }
- }
- }
-
- /*
- * If we never received a data file for the current stream, delay the
- * opening, otherwise open it right now.
- */
- if (stream->file && vstream->stream_file.trace_chunk) {
- int ret;
- char file_path[LTTNG_PATH_MAX];
- enum lttng_trace_chunk_status status;
-
- ret = utils_stream_file_path(stream->path_name,
- stream->channel_name, stream->tracefile_size,
- vstream->current_tracefile_id, NULL, file_path,
- sizeof(file_path));
- if (ret < 0) {
- goto error;
- }
-
- status = lttng_trace_chunk_open_fs_handle(
- vstream->stream_file.trace_chunk, file_path,
- O_RDONLY, 0, &vstream->stream_file.handle,
- true);
- if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
- goto error;
- }
- }
-
- if (seek_t == LTTNG_VIEWER_SEEK_LAST && vstream->index_file) {
- off_t lseek_ret;
-
- lseek_ret = fs_handle_seek(
- vstream->index_file->file, 0, SEEK_END);
- if (lseek_ret < 0) {
- goto error;
- }
- }
- if (stream->is_metadata) {
- rcu_assign_pointer(stream->trace->viewer_metadata_stream,
- vstream);
- }
-
- vstream->last_seen_rotation_count = stream->completed_rotation_count;
-
- /* Globally visible after the add unique. */
- lttng_ht_node_init_u64(&vstream->stream_n, stream->stream_handle);
- urcu_ref_init(&vstream->ref);
- lttng_ht_add_unique_u64(viewer_streams_ht, &vstream->stream_n);
-
- return vstream;
-
-error:
- if (vstream) {
- viewer_stream_destroy(vstream);
- }
- return NULL;
-}
-
-static void viewer_stream_unpublish(struct relay_viewer_stream *vstream)
-{
- int ret;
- struct lttng_ht_iter iter;
-
- iter.iter.node = &vstream->stream_n.node;
- ret = lttng_ht_del(viewer_streams_ht, &iter);
- LTTNG_ASSERT(!ret);
-}
-
-static void viewer_stream_release(struct urcu_ref *ref)
-{
- struct relay_viewer_stream *vstream = caa_container_of(ref,
- struct relay_viewer_stream, ref);
-
- if (vstream->stream->is_metadata) {
- rcu_assign_pointer(vstream->stream->trace->viewer_metadata_stream, NULL);
- }
-
- viewer_stream_unpublish(vstream);
-
- if (vstream->stream_file.handle) {
- fs_handle_close(vstream->stream_file.handle);
- vstream->stream_file.handle = NULL;
- }
- if (vstream->index_file) {
- lttng_index_file_put(vstream->index_file);
- vstream->index_file = NULL;
- }
- if (vstream->stream) {
- stream_put(vstream->stream);
- vstream->stream = NULL;
- }
- lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
- vstream->stream_file.trace_chunk = NULL;
- call_rcu(&vstream->rcu_node, viewer_stream_destroy_rcu);
-}
-
-/* Must be called with RCU read-side lock held. */
-bool viewer_stream_get(struct relay_viewer_stream *vstream)
-{
- return urcu_ref_get_unless_zero(&vstream->ref);
-}
-
-/*
- * Get viewer stream by id.
- *
- * Return viewer stream if found else NULL.
- */
-struct relay_viewer_stream *viewer_stream_get_by_id(uint64_t id)
-{
- struct lttng_ht_node_u64 *node;
- struct lttng_ht_iter iter;
- struct relay_viewer_stream *vstream = NULL;
-
- rcu_read_lock();
- lttng_ht_lookup(viewer_streams_ht, &id, &iter);
- node = lttng_ht_iter_get_node_u64(&iter);
- if (!node) {
- DBG("Relay viewer stream %" PRIu64 " not found", id);
- goto end;
- }
- vstream = caa_container_of(node, struct relay_viewer_stream, stream_n);
- if (!viewer_stream_get(vstream)) {
- vstream = NULL;
- }
-end:
- rcu_read_unlock();
- return vstream;
-}
-
-void viewer_stream_put(struct relay_viewer_stream *vstream)
-{
- rcu_read_lock();
- urcu_ref_put(&vstream->ref, viewer_stream_release);
- rcu_read_unlock();
-}
-
-void viewer_stream_close_files(struct relay_viewer_stream *vstream)
-{
- if (vstream->index_file) {
- lttng_index_file_put(vstream->index_file);
- vstream->index_file = NULL;
- }
- if (vstream->stream_file.handle) {
- fs_handle_close(vstream->stream_file.handle);
- vstream->stream_file.handle = NULL;
- }
-}
-
-void viewer_stream_sync_tracefile_array_tail(struct relay_viewer_stream *vstream)
-{
- const struct relay_stream *stream = vstream->stream;
- uint64_t seq_tail;
-
- vstream->current_tracefile_id = tracefile_array_get_file_index_tail(stream->tfa);
- seq_tail = tracefile_array_get_seq_tail(stream->tfa);
- if (seq_tail == -1ULL) {
- seq_tail = 0;
- }
- vstream->index_sent_seqcount = seq_tail;
-}
-
-/*
- * Rotate a stream to the next tracefile.
- *
- * Must be called with the rstream lock held.
- * Returns 0 on success, 1 on EOF.
- */
-int viewer_stream_rotate(struct relay_viewer_stream *vstream)
-{
- int ret;
- uint64_t new_id;
- const struct relay_stream *stream = vstream->stream;
-
- /* Detect the last tracefile to open. */
- if (stream->index_received_seqcount
- == vstream->index_sent_seqcount
- && stream->trace->session->connection_closed) {
- ret = 1;
- goto end;
- }
-
- if (stream->tracefile_count == 0) {
- /* Ignore rotation, there is none to do. */
- ret = 0;
- goto end;
- }
-
- /*
- * Try to move to the next file.
- */
- new_id = (vstream->current_tracefile_id + 1)
- % stream->tracefile_count;
- if (tracefile_array_seq_in_file(stream->tfa, new_id,
- vstream->index_sent_seqcount)) {
- vstream->current_tracefile_id = new_id;
- } else {
- uint64_t seq_tail = tracefile_array_get_seq_tail(stream->tfa);
-
- /*
- * This can only be reached on overwrite, which implies there
- * has been data written at some point, which will have set the
- * tail.
- */
- LTTNG_ASSERT(seq_tail != -1ULL);
- /*
- * We need to resync because we lag behind tail.
- */
- vstream->current_tracefile_id =
- tracefile_array_get_file_index_tail(stream->tfa);
- vstream->index_sent_seqcount = seq_tail;
- }
- viewer_stream_close_files(vstream);
- ret = 0;
-end:
- return ret;
-}
-
-void print_viewer_streams(void)
-{
- struct lttng_ht_iter iter;
- struct relay_viewer_stream *vstream;
-
- if (!viewer_streams_ht) {
- return;
- }
-
- rcu_read_lock();
- cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter, vstream,
- stream_n.node) {
- if (!viewer_stream_get(vstream)) {
- continue;
- }
- DBG("vstream %p refcount %ld stream %" PRIu64 " trace %" PRIu64
- " session %" PRIu64,
- vstream,
- vstream->ref.refcount,
- vstream->stream->stream_handle,
- vstream->stream->trace->id,
- vstream->stream->trace->session->id);
- viewer_stream_put(vstream);
- }
- rcu_read_unlock();
-}
--- /dev/null
+/*
+ * Copyright (C) 2013 Julien Desfossez <jdesfossez@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <common/common.h>
+#include <common/index/index.h>
+#include <common/compat/string.h>
+#include <common/utils.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "lttng-relayd.h"
+#include "viewer-stream.h"
+
+static void viewer_stream_destroy(struct relay_viewer_stream *vstream)
+{
+ lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
+ free(vstream->path_name);
+ free(vstream->channel_name);
+ free(vstream);
+}
+
+static void viewer_stream_destroy_rcu(struct rcu_head *head)
+{
+ struct relay_viewer_stream *vstream =
+ caa_container_of(head, struct relay_viewer_stream, rcu_node);
+
+ viewer_stream_destroy(vstream);
+}
+
+/* Relay stream's lock must be held by the caller. */
+struct relay_viewer_stream *viewer_stream_create(struct relay_stream *stream,
+ struct lttng_trace_chunk *trace_chunk,
+ enum lttng_viewer_seek seek_t)
+{
+ struct relay_viewer_stream *vstream = NULL;
+
+ ASSERT_LOCKED(stream->lock);
+
+ vstream = (relay_viewer_stream *) zmalloc(sizeof(*vstream));
+ if (!vstream) {
+ PERROR("relay viewer stream zmalloc");
+ goto error;
+ }
+
+ if (trace_chunk) {
+ const bool acquired_reference = lttng_trace_chunk_get(
+ trace_chunk);
+
+ LTTNG_ASSERT(acquired_reference);
+ }
+
+ vstream->stream_file.trace_chunk = trace_chunk;
+ vstream->path_name = lttng_strndup(stream->path_name, LTTNG_VIEWER_PATH_MAX);
+ if (vstream->path_name == NULL) {
+ PERROR("relay viewer path_name alloc");
+ goto error;
+ }
+ vstream->channel_name = lttng_strndup(stream->channel_name,
+ LTTNG_VIEWER_NAME_MAX);
+ if (vstream->channel_name == NULL) {
+ PERROR("relay viewer channel_name alloc");
+ goto error;
+ }
+
+ if (!stream_get(stream)) {
+ ERR("Cannot get stream");
+ goto error;
+ }
+ vstream->stream = stream;
+
+ if (stream->is_metadata && stream->trace->viewer_metadata_stream) {
+ ERR("Cannot attach viewer metadata stream to trace (busy).");
+ goto error;
+ }
+
+ switch (seek_t) {
+ case LTTNG_VIEWER_SEEK_BEGINNING:
+ {
+ uint64_t seq_tail = tracefile_array_get_seq_tail(stream->tfa);
+
+ if (seq_tail == -1ULL) {
+ /*
+ * Tail may not be initialized yet. Nonetheless, we know
+ * we want to send the first index once it becomes
+ * available.
+ */
+ seq_tail = 0;
+ }
+ vstream->current_tracefile_id =
+ tracefile_array_get_file_index_tail(stream->tfa);
+ vstream->index_sent_seqcount = seq_tail;
+ break;
+ }
+ case LTTNG_VIEWER_SEEK_LAST:
+ vstream->current_tracefile_id =
+ tracefile_array_get_read_file_index_head(stream->tfa);
+ /*
+ * We seek at the very end of each stream, awaiting for
+ * a future packet to eventually come in.
+ *
+ * We don't need to check the head position for -1ULL since the
+ * increment will set it to 0.
+ */
+ vstream->index_sent_seqcount =
+ tracefile_array_get_seq_head(stream->tfa) + 1;
+ break;
+ default:
+ goto error;
+ }
+
+ /*
+ * If we never received an index for the current stream, delay
+ * the opening of the index, otherwise open it right now.
+ */
+ if (stream->index_file == NULL) {
+ vstream->index_file = NULL;
+ } else if (vstream->stream_file.trace_chunk) {
+ const uint32_t connection_major = stream->trace->session->major;
+ const uint32_t connection_minor = stream->trace->session->minor;
+ enum lttng_trace_chunk_status chunk_status;
+
+ chunk_status = lttng_index_file_create_from_trace_chunk_read_only(
+ vstream->stream_file.trace_chunk,
+ stream->path_name,
+ stream->channel_name, stream->tracefile_size,
+ vstream->current_tracefile_id,
+ lttng_to_index_major(connection_major,
+ connection_minor),
+ lttng_to_index_minor(connection_major,
+ connection_minor),
+ true, &vstream->index_file);
+ if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_NO_FILE) {
+ vstream->index_file = NULL;
+ } else {
+ goto error;
+ }
+ }
+ }
+
+ /*
+ * If we never received a data file for the current stream, delay the
+ * opening, otherwise open it right now.
+ */
+ if (stream->file && vstream->stream_file.trace_chunk) {
+ int ret;
+ char file_path[LTTNG_PATH_MAX];
+ enum lttng_trace_chunk_status status;
+
+ ret = utils_stream_file_path(stream->path_name,
+ stream->channel_name, stream->tracefile_size,
+ vstream->current_tracefile_id, NULL, file_path,
+ sizeof(file_path));
+ if (ret < 0) {
+ goto error;
+ }
+
+ status = lttng_trace_chunk_open_fs_handle(
+ vstream->stream_file.trace_chunk, file_path,
+ O_RDONLY, 0, &vstream->stream_file.handle,
+ true);
+ if (status != LTTNG_TRACE_CHUNK_STATUS_OK) {
+ goto error;
+ }
+ }
+
+ if (seek_t == LTTNG_VIEWER_SEEK_LAST && vstream->index_file) {
+ off_t lseek_ret;
+
+ lseek_ret = fs_handle_seek(
+ vstream->index_file->file, 0, SEEK_END);
+ if (lseek_ret < 0) {
+ goto error;
+ }
+ }
+ if (stream->is_metadata) {
+ rcu_assign_pointer(stream->trace->viewer_metadata_stream,
+ vstream);
+ }
+
+ vstream->last_seen_rotation_count = stream->completed_rotation_count;
+
+ /* Globally visible after the add unique. */
+ lttng_ht_node_init_u64(&vstream->stream_n, stream->stream_handle);
+ urcu_ref_init(&vstream->ref);
+ lttng_ht_add_unique_u64(viewer_streams_ht, &vstream->stream_n);
+
+ return vstream;
+
+error:
+ if (vstream) {
+ viewer_stream_destroy(vstream);
+ }
+ return NULL;
+}
+
+static void viewer_stream_unpublish(struct relay_viewer_stream *vstream)
+{
+ int ret;
+ struct lttng_ht_iter iter;
+
+ iter.iter.node = &vstream->stream_n.node;
+ ret = lttng_ht_del(viewer_streams_ht, &iter);
+ LTTNG_ASSERT(!ret);
+}
+
+static void viewer_stream_release(struct urcu_ref *ref)
+{
+ struct relay_viewer_stream *vstream = caa_container_of(ref,
+ struct relay_viewer_stream, ref);
+
+ if (vstream->stream->is_metadata) {
+ rcu_assign_pointer(vstream->stream->trace->viewer_metadata_stream, NULL);
+ }
+
+ viewer_stream_unpublish(vstream);
+
+ if (vstream->stream_file.handle) {
+ fs_handle_close(vstream->stream_file.handle);
+ vstream->stream_file.handle = NULL;
+ }
+ if (vstream->index_file) {
+ lttng_index_file_put(vstream->index_file);
+ vstream->index_file = NULL;
+ }
+ if (vstream->stream) {
+ stream_put(vstream->stream);
+ vstream->stream = NULL;
+ }
+ lttng_trace_chunk_put(vstream->stream_file.trace_chunk);
+ vstream->stream_file.trace_chunk = NULL;
+ call_rcu(&vstream->rcu_node, viewer_stream_destroy_rcu);
+}
+
+/* Must be called with RCU read-side lock held. */
+bool viewer_stream_get(struct relay_viewer_stream *vstream)
+{
+ return urcu_ref_get_unless_zero(&vstream->ref);
+}
+
+/*
+ * Get viewer stream by id.
+ *
+ * Return viewer stream if found else NULL.
+ */
+struct relay_viewer_stream *viewer_stream_get_by_id(uint64_t id)
+{
+ struct lttng_ht_node_u64 *node;
+ struct lttng_ht_iter iter;
+ struct relay_viewer_stream *vstream = NULL;
+
+ rcu_read_lock();
+ lttng_ht_lookup(viewer_streams_ht, &id, &iter);
+ node = lttng_ht_iter_get_node_u64(&iter);
+ if (!node) {
+ DBG("Relay viewer stream %" PRIu64 " not found", id);
+ goto end;
+ }
+ vstream = caa_container_of(node, struct relay_viewer_stream, stream_n);
+ if (!viewer_stream_get(vstream)) {
+ vstream = NULL;
+ }
+end:
+ rcu_read_unlock();
+ return vstream;
+}
+
+void viewer_stream_put(struct relay_viewer_stream *vstream)
+{
+ rcu_read_lock();
+ urcu_ref_put(&vstream->ref, viewer_stream_release);
+ rcu_read_unlock();
+}
+
+void viewer_stream_close_files(struct relay_viewer_stream *vstream)
+{
+ if (vstream->index_file) {
+ lttng_index_file_put(vstream->index_file);
+ vstream->index_file = NULL;
+ }
+ if (vstream->stream_file.handle) {
+ fs_handle_close(vstream->stream_file.handle);
+ vstream->stream_file.handle = NULL;
+ }
+}
+
+void viewer_stream_sync_tracefile_array_tail(struct relay_viewer_stream *vstream)
+{
+ const struct relay_stream *stream = vstream->stream;
+ uint64_t seq_tail;
+
+ vstream->current_tracefile_id = tracefile_array_get_file_index_tail(stream->tfa);
+ seq_tail = tracefile_array_get_seq_tail(stream->tfa);
+ if (seq_tail == -1ULL) {
+ seq_tail = 0;
+ }
+ vstream->index_sent_seqcount = seq_tail;
+}
+
+/*
+ * Rotate a stream to the next tracefile.
+ *
+ * Must be called with the rstream lock held.
+ * Returns 0 on success, 1 on EOF.
+ */
+int viewer_stream_rotate(struct relay_viewer_stream *vstream)
+{
+ int ret;
+ uint64_t new_id;
+ const struct relay_stream *stream = vstream->stream;
+
+ /* Detect the last tracefile to open. */
+ if (stream->index_received_seqcount
+ == vstream->index_sent_seqcount
+ && stream->trace->session->connection_closed) {
+ ret = 1;
+ goto end;
+ }
+
+ if (stream->tracefile_count == 0) {
+ /* Ignore rotation, there is none to do. */
+ ret = 0;
+ goto end;
+ }
+
+ /*
+ * Try to move to the next file.
+ */
+ new_id = (vstream->current_tracefile_id + 1)
+ % stream->tracefile_count;
+ if (tracefile_array_seq_in_file(stream->tfa, new_id,
+ vstream->index_sent_seqcount)) {
+ vstream->current_tracefile_id = new_id;
+ } else {
+ uint64_t seq_tail = tracefile_array_get_seq_tail(stream->tfa);
+
+ /*
+ * This can only be reached on overwrite, which implies there
+ * has been data written at some point, which will have set the
+ * tail.
+ */
+ LTTNG_ASSERT(seq_tail != -1ULL);
+ /*
+ * We need to resync because we lag behind tail.
+ */
+ vstream->current_tracefile_id =
+ tracefile_array_get_file_index_tail(stream->tfa);
+ vstream->index_sent_seqcount = seq_tail;
+ }
+ viewer_stream_close_files(vstream);
+ ret = 0;
+end:
+ return ret;
+}
+
+void print_viewer_streams(void)
+{
+ struct lttng_ht_iter iter;
+ struct relay_viewer_stream *vstream;
+
+ if (!viewer_streams_ht) {
+ return;
+ }
+
+ rcu_read_lock();
+ cds_lfht_for_each_entry(viewer_streams_ht->ht, &iter.iter, vstream,
+ stream_n.node) {
+ if (!viewer_stream_get(vstream)) {
+ continue;
+ }
+ DBG("vstream %p refcount %ld stream %" PRIu64 " trace %" PRIu64
+ " session %" PRIu64,
+ vstream,
+ vstream->ref.refcount,
+ vstream->stream->stream_handle,
+ vstream->stream->trace->id,
+ vstream->stream->trace->session->id);
+ viewer_stream_put(vstream);
+ }
+ rcu_read_unlock();
+}
#include <stdint.h>
#include <sys/types.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct fs_handle;
struct fd_tracker;
*/
void fd_tracker_log(struct fd_tracker *tracker);
-/*
- * Marks the handle as the most recently used and marks the 'fd' as
- * "in-use". This prevents the tracker from recycling the underlying
- * file descriptor while it is actively being used by a thread.
- *
- * Don't forget that the tracker may be initiating an fd 'suspension'
- * from another thread as the need to free an fd slot may arise from any
- * thread within the daemon.
- *
- * Note that a restorable fd should never be held for longer than
- * strictly necessary (e.g. the duration of a syscall()).
- *
- * Returns the fd on success, otherwise a negative value may be returned
- * if the restoration of the fd failed.
- */
-int fs_handle_get_fd(struct fs_handle *handle);
-
-/*
- * Used by the application to signify that it is no longer using the
- * underlying fd and that it may be suspended.
- */
-void fs_handle_put_fd(struct fs_handle *handle);
-
-/*
- * Unlink the file associated to an fs_handle. Note that the unlink
- * operation will not be performed immediately. It will only be performed
- * once all references to the underlying file (through other fs_handle objects)
- * have been released.
- *
- * However, note that the file will be renamed so as to provide the observable
- * effect of an unlink(), that is removing a name from the filesystem.
- *
- * Returns 0 on success, otherwise a negative value will be returned
- * if the operation failed.
- */
-int fs_handle_unlink(struct fs_handle *handle);
-
-/*
- * Frees the handle and discards the underlying fd.
- */
-int fs_handle_close(struct fs_handle *handle);
+#ifdef __cplusplus
+}
+#endif
#endif /* FD_TRACKER_H */
#include <common/fd-tracker/fd-tracker.h>
#include <common/macros.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct lttng_poll_event;
/*
struct lttng_directory_handle *handle,
const char *path);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FD_TRACKER_UTILS_H */
#include <common/macros.h>
#include <stdio.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct fs_handle;
/*
off_t fs_handle_seek(struct fs_handle *handle, off_t offset, int whence);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* FS_HANDLE_H */
#include <common/fs-handle.h>
#include <common/trace-chunk.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct lttng_index_file {
struct fs_handle *file;
uint32_t major;
void lttng_index_file_get(struct lttng_index_file *index_file);
void lttng_index_file_put(struct lttng_index_file *index_file);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* _INDEX_H */
#include <common/macros.h>
#include <common/trace-chunk.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct lttng_trace_chunk_registry;
/*
unsigned int lttng_trace_chunk_registry_put_each_chunk(
const struct lttng_trace_chunk_registry *registry);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* LTTNG_TRACE_CHUNK_REGISTRY_H */
#include <common/error.h>
#include <common/fd-tracker/fd-tracker.h>
#include <common/fd-tracker/utils.h>
+#include <common/fs-handle.h>
#include <common/fs-handle-internal.h>
#include <common/hashtable/hashtable.h>
#include <common/hashtable/utils.h>
test_condition_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBLTTNG_CTL) $(DL_LIBS)
# relayd backward compat for groou-by-session utilities
-test_relayd_backward_compat_group_by_session_SOURCES = test_relayd_backward_compat_group_by_session.c
+test_relayd_backward_compat_group_by_session_SOURCES = test_relayd_backward_compat_group_by_session.cpp
test_relayd_backward_compat_group_by_session_LDADD = $(LIBTAP) $(LIBCOMMON) $(RELAYD_OBJS)
test_relayd_backward_compat_group_by_session_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src/bin/lttng-relayd
#include <common/compat/directory-handle.h>
#include <common/compat/errno.h>
#include <common/error.h>
+#include <common/fs-handle.h>
#include <common/fd-tracker/fd-tracker.h>
/* For error.h */
+++ /dev/null
-/*
- * Copyright (C) 2019 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <tap/tap.h>
-
-#include <common/time.h>
-
-#include "backward-compatibility-group-by.h"
-
-/* Number of TAP tests in this file */
-#define NUM_TESTS_PER_TEST 1
-
-struct test {
- const char *stream_path;
- const char *session_name;
- const char *hostname;
- const char *creation_time;
- const char *extra_path;
- const char *leftover;
- bool is_valid;
-};
-
-int lttng_opt_quiet;
-int lttng_opt_mi;
-int lttng_opt_verbose;
-
-struct test tests[] = {
- /* Default name session streaming. */
- {"hostname/auto-20190918-164429/ust/uid/1000/64-bit",
- "auto-20190918-164429", "hostname",
- "20190918-164429", "", "ust/uid/1000/64-bit",
- true},
- /* Custom default name session */
- {"hostname/custom_auto-20190319-120000/ust/uid/1000/64-bit",
- "custom_auto-20190319-120000", "hostname",
- "20190319-120000", "", "ust/uid/1000/64-bit",
- true},
- /* Named session streaming */
- {"hostname/test-20190918-164709/ust/uid/1000/64-bit", "test",
- "hostname", "20190918-164709", "",
- "ust/uid/1000/64-bit", true},
- /* Default session snapshot streaming */
- {"hostname//snapshot-1-20190918-164829-0/ust//uid/1000/64-bit",
- "my_session", "hostname", "", "",
- "snapshot-1-20190918-164829-0/ust//uid/1000/64-bit",
- true},
- /* Named session snapshot streaming */
- {"hostname//snapshot-1-20190918-175919-0/ust//uid/1000/64-bit",
- "my_session", "hostname", "", "",
- "snapshot-1-20190918-175919-0/ust//uid/1000/64-bit",
- true},
- /* Default name session, live */
- {"hostname//auto-20190918-171641/ust/uid/1000/64-bit",
- "auto-20190918-171641", "hostname",
- "20190918-171641", "", "ust/uid/1000/64-bit",
- true},
- /* Named session, live */
- {"hostname//test-20190918-180333//ust/uid/1000/64-bit",
- "test-20190918-180333", "hostname",
- "20190918-180333", "", "/ust/uid/1000/64-bit",
- true},
- /* Default name session, streaming & live , extra path */
- {"hostname/extra/path/ust/uid/1000/64-bit",
- "auto-20190919-122110", "hostname",
- "20190919-122110", "extra",
- "path/ust/uid/1000/64-bit", true},
- /* Named session, live, extra path */
- {"hostname/extra/path/ust/uid/1000/64-bit", "test", "hostname",
- "", "extra", "path/ust/uid/1000/64-bit", true},
- /* Named session, snapshot, extra path */
- {"hostname/extra/path/snapshot-1-20190919-140702-0/ust//uid/1000/64-bit",
- "test", "hostname", "", "extra",
- "path/snapshot-1-20190919-140702-0/ust//uid/1000/64-bit",
- true},
- /* Corner cases*/
- /* Named session with valid datetime in it */
- /* Default name session, extra path with session name in it*/
- {"hostname/test-20190319-120000-20190918-180921/ust/uid/1000/64-bit",
- "test-20190319-120000", "hostname",
- "20190918-180921", "", "ust/uid/1000/64-bit",
- true},
- /* Empty path */
- {"", "test", "", "", "", "", false},
- /* Path without second token */
- {"hostname", "test", "hostname", "", "", "", false},
- /* No leftover */
- {"hostname/test", "test", "hostname", "", "", "", true},
- /* Path with ession name but no datetime */
- {"hostname/test/ust/uid/1000/64-bit", "test", "hostname", "",
- "", "ust/uid/1000/64-bit", true},
-};
-
-static char *craft_expected(struct test *test, time_t relay_session_creation_time)
-{
- int ret;
- char *result = NULL;
- char relay_session_creation_datetime[DATETIME_STR_LEN];
-
- ret = time_to_datetime_str(relay_session_creation_time,
- relay_session_creation_datetime,
- sizeof(relay_session_creation_datetime));
- if (ret < 0) {
- result = NULL;
- goto end;
- }
-
- ret = asprintf(&result, "%s/%s-%s/%s%s%s", test->session_name,
- test->hostname,
- test->creation_time[0] == '\0' ?
- relay_session_creation_datetime :
- test->creation_time,
- test->extra_path,
- test->extra_path[0] != '\0' ? "/" : "", test->leftover);
- if (ret < 0) {
- result = NULL;
- goto end;
- }
-end:
- return result;
-}
-
-int main(int argc, char **argv)
-{
- int i;
- int num_test = sizeof(tests) / sizeof(struct test);
- const time_t test_time = time(NULL);
-
- plan_tests(NUM_TESTS_PER_TEST * num_test);
- diag("Backward compatibility utils for lttng-relayd --group-by-session");
-
- if (test_time == (time_t) -1) {
- perror("Failed to sample time");
- return exit_status();
- }
-
- for (i = 0; i < num_test; i++) {
- char *expected = NULL;
- char *result = NULL;
-
- expected = craft_expected(&tests[i], test_time);
- if (!expected) {
- fprintf(stderr, "Failed to craft expected output\n");
- goto loop;
- }
-
- result = backward_compat_group_by_session(tests[i].stream_path,
- tests[i].session_name, test_time);
- if (!result && tests[i].is_valid) {
- fprintf(stderr, "Failed to get result\n");
- goto loop;
- } else if (!result && tests[i].is_valid == false) {
- pass("Returned null as expected");
- goto loop;
- }
-
- ok(strncmp(expected, result, strlen(expected)) == 0,
- "In: %s, out: %s, expected: %s",
- tests[i].stream_path, result, expected);
- loop:
- free(expected);
- free(result);
- }
- return exit_status();
-}
--- /dev/null
+/*
+ * Copyright (C) 2019 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tap/tap.h>
+
+#include <common/time.h>
+
+#include "backward-compatibility-group-by.h"
+
+/* Number of TAP tests in this file */
+#define NUM_TESTS_PER_TEST 1
+
+struct test {
+ const char *stream_path;
+ const char *session_name;
+ const char *hostname;
+ const char *creation_time;
+ const char *extra_path;
+ const char *leftover;
+ bool is_valid;
+};
+
+int lttng_opt_quiet;
+int lttng_opt_mi;
+int lttng_opt_verbose;
+
+struct test tests[] = {
+ /* Default name session streaming. */
+ {"hostname/auto-20190918-164429/ust/uid/1000/64-bit",
+ "auto-20190918-164429", "hostname",
+ "20190918-164429", "", "ust/uid/1000/64-bit",
+ true},
+ /* Custom default name session */
+ {"hostname/custom_auto-20190319-120000/ust/uid/1000/64-bit",
+ "custom_auto-20190319-120000", "hostname",
+ "20190319-120000", "", "ust/uid/1000/64-bit",
+ true},
+ /* Named session streaming */
+ {"hostname/test-20190918-164709/ust/uid/1000/64-bit", "test",
+ "hostname", "20190918-164709", "",
+ "ust/uid/1000/64-bit", true},
+ /* Default session snapshot streaming */
+ {"hostname//snapshot-1-20190918-164829-0/ust//uid/1000/64-bit",
+ "my_session", "hostname", "", "",
+ "snapshot-1-20190918-164829-0/ust//uid/1000/64-bit",
+ true},
+ /* Named session snapshot streaming */
+ {"hostname//snapshot-1-20190918-175919-0/ust//uid/1000/64-bit",
+ "my_session", "hostname", "", "",
+ "snapshot-1-20190918-175919-0/ust//uid/1000/64-bit",
+ true},
+ /* Default name session, live */
+ {"hostname//auto-20190918-171641/ust/uid/1000/64-bit",
+ "auto-20190918-171641", "hostname",
+ "20190918-171641", "", "ust/uid/1000/64-bit",
+ true},
+ /* Named session, live */
+ {"hostname//test-20190918-180333//ust/uid/1000/64-bit",
+ "test-20190918-180333", "hostname",
+ "20190918-180333", "", "/ust/uid/1000/64-bit",
+ true},
+ /* Default name session, streaming & live , extra path */
+ {"hostname/extra/path/ust/uid/1000/64-bit",
+ "auto-20190919-122110", "hostname",
+ "20190919-122110", "extra",
+ "path/ust/uid/1000/64-bit", true},
+ /* Named session, live, extra path */
+ {"hostname/extra/path/ust/uid/1000/64-bit", "test", "hostname",
+ "", "extra", "path/ust/uid/1000/64-bit", true},
+ /* Named session, snapshot, extra path */
+ {"hostname/extra/path/snapshot-1-20190919-140702-0/ust//uid/1000/64-bit",
+ "test", "hostname", "", "extra",
+ "path/snapshot-1-20190919-140702-0/ust//uid/1000/64-bit",
+ true},
+ /* Corner cases*/
+ /* Named session with valid datetime in it */
+ /* Default name session, extra path with session name in it*/
+ {"hostname/test-20190319-120000-20190918-180921/ust/uid/1000/64-bit",
+ "test-20190319-120000", "hostname",
+ "20190918-180921", "", "ust/uid/1000/64-bit",
+ true},
+ /* Empty path */
+ {"", "test", "", "", "", "", false},
+ /* Path without second token */
+ {"hostname", "test", "hostname", "", "", "", false},
+ /* No leftover */
+ {"hostname/test", "test", "hostname", "", "", "", true},
+ /* Path with ession name but no datetime */
+ {"hostname/test/ust/uid/1000/64-bit", "test", "hostname", "",
+ "", "ust/uid/1000/64-bit", true},
+};
+
+static char *craft_expected(struct test *test, time_t relay_session_creation_time)
+{
+ int ret;
+ char *result = NULL;
+ char relay_session_creation_datetime[DATETIME_STR_LEN];
+
+ ret = time_to_datetime_str(relay_session_creation_time,
+ relay_session_creation_datetime,
+ sizeof(relay_session_creation_datetime));
+ if (ret < 0) {
+ result = NULL;
+ goto end;
+ }
+
+ ret = asprintf(&result, "%s/%s-%s/%s%s%s", test->session_name,
+ test->hostname,
+ test->creation_time[0] == '\0' ?
+ relay_session_creation_datetime :
+ test->creation_time,
+ test->extra_path,
+ test->extra_path[0] != '\0' ? "/" : "", test->leftover);
+ if (ret < 0) {
+ result = NULL;
+ goto end;
+ }
+end:
+ return result;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int num_test = sizeof(tests) / sizeof(struct test);
+ const time_t test_time = time(NULL);
+
+ plan_tests(NUM_TESTS_PER_TEST * num_test);
+ diag("Backward compatibility utils for lttng-relayd --group-by-session");
+
+ if (test_time == (time_t) -1) {
+ perror("Failed to sample time");
+ return exit_status();
+ }
+
+ for (i = 0; i < num_test; i++) {
+ char *expected = NULL;
+ char *result = NULL;
+
+ expected = craft_expected(&tests[i], test_time);
+ if (!expected) {
+ fprintf(stderr, "Failed to craft expected output\n");
+ goto loop;
+ }
+
+ result = backward_compat_group_by_session(tests[i].stream_path,
+ tests[i].session_name, test_time);
+ if (!result && tests[i].is_valid) {
+ fprintf(stderr, "Failed to get result\n");
+ goto loop;
+ } else if (!result && tests[i].is_valid == false) {
+ pass("Returned null as expected");
+ goto loop;
+ }
+
+ ok(strncmp(expected, result, strlen(expected)) == 0,
+ "In: %s, out: %s, expected: %s",
+ tests[i].stream_path, result, expected);
+ loop:
+ free(expected);
+ free(result);
+ }
+ return exit_status();
+}