bin: compile lttng-relayd as a C++
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 3 Sep 2021 21:31:29 +0000 (17:31 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Wed, 17 Nov 2021 23:29:08 +0000 (18:29 -0500)
Same as the previous commit, but change lttng-relayd to be a C++
program.  In addition to what was mentioned in previous commits:

 - Test test_relayd_backward_compat_group_by_session uses an object file
   from the relayd binary, so it's simpler to change this test to be C++
   as well.

 - There are some declarations related to fs_handle in fd-tracker.h.
   Remove them, as they are already in fs-handle.h.

Change-Id: I72c5fdecd2e82c30633563a47bd641af1dcfa29c
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
50 files changed:
src/bin/lttng-relayd/Makefile.am
src/bin/lttng-relayd/backward-compatibility-group-by.c [deleted file]
src/bin/lttng-relayd/backward-compatibility-group-by.cpp [new file with mode: 0644]
src/bin/lttng-relayd/cmd-2-1.c [deleted file]
src/bin/lttng-relayd/cmd-2-1.cpp [new file with mode: 0644]
src/bin/lttng-relayd/cmd-2-11.c [deleted file]
src/bin/lttng-relayd/cmd-2-11.cpp [new file with mode: 0644]
src/bin/lttng-relayd/cmd-2-2.c [deleted file]
src/bin/lttng-relayd/cmd-2-2.cpp [new file with mode: 0644]
src/bin/lttng-relayd/cmd-2-4.c [deleted file]
src/bin/lttng-relayd/cmd-2-4.cpp [new file with mode: 0644]
src/bin/lttng-relayd/connection.c [deleted file]
src/bin/lttng-relayd/connection.cpp [new file with mode: 0644]
src/bin/lttng-relayd/ctf-trace.c [deleted file]
src/bin/lttng-relayd/ctf-trace.cpp [new file with mode: 0644]
src/bin/lttng-relayd/health-relayd.c [deleted file]
src/bin/lttng-relayd/health-relayd.cpp [new file with mode: 0644]
src/bin/lttng-relayd/index.c [deleted file]
src/bin/lttng-relayd/index.cpp [new file with mode: 0644]
src/bin/lttng-relayd/live.c [deleted file]
src/bin/lttng-relayd/live.cpp [new file with mode: 0644]
src/bin/lttng-relayd/main.c [deleted file]
src/bin/lttng-relayd/main.cpp [new file with mode: 0644]
src/bin/lttng-relayd/session.c [deleted file]
src/bin/lttng-relayd/session.cpp [new file with mode: 0644]
src/bin/lttng-relayd/sessiond-trace-chunks.c [deleted file]
src/bin/lttng-relayd/sessiond-trace-chunks.cpp [new file with mode: 0644]
src/bin/lttng-relayd/stream.c [deleted file]
src/bin/lttng-relayd/stream.cpp [new file with mode: 0644]
src/bin/lttng-relayd/tcp_keep_alive.c [deleted file]
src/bin/lttng-relayd/tcp_keep_alive.cpp [new file with mode: 0644]
src/bin/lttng-relayd/tracefile-array.c [deleted file]
src/bin/lttng-relayd/tracefile-array.cpp [new file with mode: 0644]
src/bin/lttng-relayd/utils.c [deleted file]
src/bin/lttng-relayd/utils.cpp [new file with mode: 0644]
src/bin/lttng-relayd/viewer-session.c [deleted file]
src/bin/lttng-relayd/viewer-session.cpp [new file with mode: 0644]
src/bin/lttng-relayd/viewer-session.h
src/bin/lttng-relayd/viewer-stream.c [deleted file]
src/bin/lttng-relayd/viewer-stream.cpp [new file with mode: 0644]
src/common/fd-tracker/fd-tracker.h
src/common/fd-tracker/utils.h
src/common/fs-handle.h
src/common/index/index.h
src/common/trace-chunk-registry.h
src/common/trace-chunk.c
tests/unit/Makefile.am
tests/unit/test_fd_tracker.c
tests/unit/test_relayd_backward_compat_group_by_session.c [deleted file]
tests/unit/test_relayd_backward_compat_group_by_session.cpp [new file with mode: 0644]

index f6d5b29501f17295dcc9fc1e0e8641b502be6710..51b23296835d1f14e1cd23958033582da5229481 100644 (file)
@@ -9,23 +9,23 @@ endif
 
 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) \
diff --git a/src/bin/lttng-relayd/backward-compatibility-group-by.c b/src/bin/lttng-relayd/backward-compatibility-group-by.c
deleted file mode 100644 (file)
index 9e144f9..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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(&regex, 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(&regex, 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(&regex, 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(&regex);
-error:
-       free(local_copy);
-       free(partial_base_path);
-       free(datetime);
-       return filepath_per_session;
-}
diff --git a/src/bin/lttng-relayd/backward-compatibility-group-by.cpp b/src/bin/lttng-relayd/backward-compatibility-group-by.cpp
new file mode 100644 (file)
index 0000000..f0454a5
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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(&regex, 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(&regex, 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(&regex, 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(&regex);
+error:
+       free(local_copy);
+       free(partial_base_path);
+       free(datetime);
+       return filepath_per_session;
+}
diff --git a/src/bin/lttng-relayd/cmd-2-1.c b/src/bin/lttng-relayd/cmd-2-1.c
deleted file mode 100644 (file)
index 1f939dd..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/cmd-2-1.cpp b/src/bin/lttng-relayd/cmd-2-1.cpp
new file mode 100644 (file)
index 0000000..1f939dd
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/cmd-2-11.c b/src/bin/lttng-relayd/cmd-2-11.c
deleted file mode 100644 (file)
index aa97dd0..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/cmd-2-11.cpp b/src/bin/lttng-relayd/cmd-2-11.cpp
new file mode 100644 (file)
index 0000000..aa97dd0
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/cmd-2-2.c b/src/bin/lttng-relayd/cmd-2-2.c
deleted file mode 100644 (file)
index bd18714..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/cmd-2-2.cpp b/src/bin/lttng-relayd/cmd-2-2.cpp
new file mode 100644 (file)
index 0000000..bd18714
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/cmd-2-4.c b/src/bin/lttng-relayd/cmd-2-4.c
deleted file mode 100644 (file)
index 7abc371..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/cmd-2-4.cpp b/src/bin/lttng-relayd/cmd-2-4.cpp
new file mode 100644 (file)
index 0000000..7abc371
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/connection.c b/src/bin/lttng-relayd/connection.c
deleted file mode 100644 (file)
index e50784b..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/connection.cpp b/src/bin/lttng-relayd/connection.cpp
new file mode 100644 (file)
index 0000000..98ffad2
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/ctf-trace.c b/src/bin/lttng-relayd/ctf-trace.c
deleted file mode 100644 (file)
index f2af1a3..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/ctf-trace.cpp b/src/bin/lttng-relayd/ctf-trace.cpp
new file mode 100644 (file)
index 0000000..7a3cbde
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/health-relayd.c b/src/bin/lttng-relayd/health-relayd.c
deleted file mode 100644 (file)
index 57fa70f..0000000
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/health-relayd.cpp b/src/bin/lttng-relayd/health-relayd.cpp
new file mode 100644 (file)
index 0000000..2e532bd
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/index.c b/src/bin/lttng-relayd/index.c
deleted file mode 100644 (file)
index f7d22b3..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * 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);
-}
diff --git a/src/bin/lttng-relayd/index.cpp b/src/bin/lttng-relayd/index.cpp
new file mode 100644 (file)
index 0000000..ef00a6e
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * 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);
+}
diff --git a/src/bin/lttng-relayd/live.c b/src/bin/lttng-relayd/live.c
deleted file mode 100644 (file)
index 962fe0f..0000000
+++ /dev/null
@@ -1,2737 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/live.cpp b/src/bin/lttng-relayd/live.cpp
new file mode 100644 (file)
index 0000000..92c3bf4
--- /dev/null
@@ -0,0 +1,2739 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/main.c b/src/bin/lttng-relayd/main.c
deleted file mode 100644 (file)
index fb1817c..0000000
+++ /dev/null
@@ -1,4444 +0,0 @@
-/*
- * 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(&lttng_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,
-                               &current_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(&current_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 ? &current_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);
-       }
-}
diff --git a/src/bin/lttng-relayd/main.cpp b/src/bin/lttng-relayd/main.cpp
new file mode 100644 (file)
index 0000000..bc3c56f
--- /dev/null
@@ -0,0 +1,4441 @@
+/*
+ * 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(&lttng_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,
+                               &current_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(&current_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 ? &current_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);
+       }
+}
diff --git a/src/bin/lttng-relayd/session.c b/src/bin/lttng-relayd/session.c
deleted file mode 100644 (file)
index 0a535cf..0000000
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * 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();
-}
diff --git a/src/bin/lttng-relayd/session.cpp b/src/bin/lttng-relayd/session.cpp
new file mode 100644 (file)
index 0000000..5fa3966
--- /dev/null
@@ -0,0 +1,616 @@
+/*
+ * 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();
+}
diff --git a/src/bin/lttng-relayd/sessiond-trace-chunks.c b/src/bin/lttng-relayd/sessiond-trace-chunks.c
deleted file mode 100644 (file)
index 2f98c3d..0000000
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/sessiond-trace-chunks.cpp b/src/bin/lttng-relayd/sessiond-trace-chunks.cpp
new file mode 100644 (file)
index 0000000..e0c46e3
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/stream.c b/src/bin/lttng-relayd/stream.c
deleted file mode 100644 (file)
index 1eaca14..0000000
+++ /dev/null
@@ -1,1383 +0,0 @@
-/*
- * 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();
-}
diff --git a/src/bin/lttng-relayd/stream.cpp b/src/bin/lttng-relayd/stream.cpp
new file mode 100644 (file)
index 0000000..1580092
--- /dev/null
@@ -0,0 +1,1383 @@
+/*
+ * 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();
+}
diff --git a/src/bin/lttng-relayd/tcp_keep_alive.c b/src/bin/lttng-relayd/tcp_keep_alive.c
deleted file mode 100644 (file)
index 94bf4ab..0000000
+++ /dev/null
@@ -1,597 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/tcp_keep_alive.cpp b/src/bin/lttng-relayd/tcp_keep_alive.cpp
new file mode 100644 (file)
index 0000000..94bf4ab
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * 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;
+}
diff --git a/src/bin/lttng-relayd/tracefile-array.c b/src/bin/lttng-relayd/tracefile-array.c
deleted file mode 100644 (file)
index 996c43e..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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;
-       }
-}
diff --git a/src/bin/lttng-relayd/tracefile-array.cpp b/src/bin/lttng-relayd/tracefile-array.cpp
new file mode 100644 (file)
index 0000000..05012b6
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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;
+       }
+}
diff --git a/src/bin/lttng-relayd/utils.c b/src/bin/lttng-relayd/utils.c
deleted file mode 100644 (file)
index 599f7ff..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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);
-       }
-}
diff --git a/src/bin/lttng-relayd/utils.cpp b/src/bin/lttng-relayd/utils.cpp
new file mode 100644 (file)
index 0000000..599f7ff
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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);
+       }
+}
diff --git a/src/bin/lttng-relayd/viewer-session.c b/src/bin/lttng-relayd/viewer-session.c
deleted file mode 100644 (file)
index b2aba61..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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;
-}
diff --git a/src/bin/lttng-relayd/viewer-session.cpp b/src/bin/lttng-relayd/viewer-session.cpp
new file mode 100644 (file)
index 0000000..9bed097
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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;
+}
index cbf4aa157dcfff4c8071ca3792191a8988a8e199..2a685f948f15df365f6bb7bf3ee60a8715000379 100644 (file)
@@ -20,6 +20,7 @@
 #include <common/trace-chunk.h>
 
 #include "session.h"
+#include "lttng-viewer-abi.h"
 
 struct relay_viewer_session {
        /*
diff --git a/src/bin/lttng-relayd/viewer-stream.c b/src/bin/lttng-relayd/viewer-stream.c
deleted file mode 100644 (file)
index 494b0e6..0000000
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * 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();
-}
diff --git a/src/bin/lttng-relayd/viewer-stream.cpp b/src/bin/lttng-relayd/viewer-stream.cpp
new file mode 100644 (file)
index 0000000..7a66fde
--- /dev/null
@@ -0,0 +1,389 @@
+/*
+ * 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();
+}
index ec859c085b67fbd4b6edeacd0631129e27734bf9..5801622e22995610b4e5083d5623163f095ebe44 100644 (file)
 #include <stdint.h>
 #include <sys/types.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 struct fs_handle;
 struct fd_tracker;
 
@@ -138,46 +142,8 @@ int fd_tracker_close_unsuspendable_fd(struct fd_tracker *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 */
index 829b56b6d2ab5f82efb3e634de0108293b10db31..ac614c7b17fc50f483b75b32a24cd48054a1f45c 100644 (file)
 #include <common/fd-tracker/fd-tracker.h>
 #include <common/macros.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 struct lttng_poll_event;
 
 /*
@@ -46,4 +50,8 @@ struct lttng_directory_handle *fd_tracker_create_directory_handle_from_handle(
                struct lttng_directory_handle *handle,
                const char *path);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* FD_TRACKER_UTILS_H */
index be6419c88315343b2980b415a54fc84ca0ae475e..afc5abc48571ed1510e91b56ba13d9d7151a8819 100644 (file)
 #include <common/macros.h>
 #include <stdio.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 struct fs_handle;
 
 /*
@@ -63,4 +67,8 @@ int fs_handle_truncate(struct fs_handle *handle, off_t offset);
 
 off_t fs_handle_seek(struct fs_handle *handle, off_t offset, int whence);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* FS_HANDLE_H */
index 25ecc1975d2c38701ea7f50f0ab8fd896fae05d5..c7bc656823b9138fbd15827c273a8cec82e2a3bd 100644 (file)
 #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;
@@ -52,4 +56,8 @@ int lttng_index_file_read(const struct lttng_index_file *index_file,
 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 */
index 0eee0c21c4e918ce14d2c6ebccc8f21cf25c9379..f61e560808486c37b1640d3ed04bce0a6cf48555 100644 (file)
 #include <common/macros.h>
 #include <common/trace-chunk.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 struct lttng_trace_chunk_registry;
 
 /*
@@ -92,4 +96,8 @@ lttng_trace_chunk_registry_find_anonymous_chunk(
 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 */
index 4bc3dbae0c79546c53e4a42f123678889c7ff497..d04d51637e603fe763a067a3271917d1da5e80a7 100644 (file)
@@ -12,6 +12,7 @@
 #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>
index c8deb2ec337026b2dc2a5024479f023c68c26500..e1e538657c821fa625629263df24583e9b59b650 100644 (file)
@@ -146,7 +146,7 @@ test_condition_SOURCES = test_condition.c
 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
 
index 73010c4c1a2d5163aaefe93f87aabf4593e29e78..6df179f9d7516bfe8ffb2ef042164e867362781a 100644 (file)
@@ -24,6 +24,7 @@
 #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 */
diff --git a/tests/unit/test_relayd_backward_compat_group_by_session.c b/tests/unit/test_relayd_backward_compat_group_by_session.c
deleted file mode 100644 (file)
index dee0fab..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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();
-}
diff --git a/tests/unit/test_relayd_backward_compat_group_by_session.cpp b/tests/unit/test_relayd_backward_compat_group_by_session.cpp
new file mode 100644 (file)
index 0000000..dee0fab
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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();
+}
This page took 0.322605 seconds and 4 git commands to generate.