| 1 | /* |
| 2 | * Copyright (C) 2019 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com> |
| 3 | * |
| 4 | * SPDX-License-Identifier: GPL-2.0-only |
| 5 | * |
| 6 | */ |
| 7 | |
| 8 | #include "backward-compatibility-group-by.hpp" |
| 9 | #include "common/time.hpp" |
| 10 | |
| 11 | #include <common/common.hpp> |
| 12 | #include <common/defaults.hpp> |
| 13 | #include <common/utils.hpp> |
| 14 | |
| 15 | #include <regex.h> |
| 16 | #include <stdio.h> |
| 17 | #include <stdlib.h> |
| 18 | #include <string.h> |
| 19 | |
| 20 | #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]$" |
| 21 | |
| 22 | /* |
| 23 | * Provide support for --group-output-by-session for producer >= 2.4 and < 2.11. |
| 24 | * Take the stream path, extract all available information, craft a new path to |
| 25 | * the best of our ability enforcing the group by session. |
| 26 | * |
| 27 | * Return the allocated string containing the new stream path or else NULL. |
| 28 | */ |
| 29 | char *backward_compat_group_by_session(const char *path, |
| 30 | const char *local_session_name, |
| 31 | time_t relay_session_creation_time) |
| 32 | { |
| 33 | int ret; |
| 34 | size_t len; |
| 35 | char *leftover_ptr; |
| 36 | char *local_copy = nullptr; |
| 37 | char *datetime = nullptr; |
| 38 | char *partial_base_path = nullptr; |
| 39 | char *filepath_per_session = nullptr; |
| 40 | const char *second_token_ptr; |
| 41 | const char *leftover_second_token_ptr; |
| 42 | const char *hostname_ptr; |
| 43 | regex_t regex; |
| 44 | |
| 45 | LTTNG_ASSERT(path); |
| 46 | LTTNG_ASSERT(local_session_name); |
| 47 | LTTNG_ASSERT(local_session_name[0] != '\0'); |
| 48 | |
| 49 | DBG("Parsing path \"%s\" of session \"%s\" to create a new path that is grouped by session", |
| 50 | path, |
| 51 | local_session_name); |
| 52 | |
| 53 | /* Get a local copy for strtok */ |
| 54 | local_copy = strdup(path); |
| 55 | if (!local_copy) { |
| 56 | PERROR("Failed to parse session path: couldn't copy input path"); |
| 57 | goto error; |
| 58 | } |
| 59 | |
| 60 | /* |
| 61 | * The use of strtok with '/' as delimiter is valid since we refuse '/' |
| 62 | * in session name and '/' is not a valid hostname character based on |
| 63 | * RFC-952 [1], RFC-921 [2] and refined in RFC-1123 [3]. |
| 64 | * [1] https://tools.ietf.org/html/rfc952 |
| 65 | * [2] https://tools.ietf.org/html/rfc921 |
| 66 | * [3] https://tools.ietf.org/html/rfc1123#page-13 |
| 67 | */ |
| 68 | |
| 69 | /* |
| 70 | * Get the hostname and possible session_name. |
| 71 | * Note that we can get the hostname and session name from the |
| 72 | * relay_session object we already have. Still, it is easier to |
| 73 | * tokenized the passed path to obtain the start of the path leftover. |
| 74 | */ |
| 75 | hostname_ptr = strtok_r(local_copy, "/", &leftover_ptr); |
| 76 | if (!hostname_ptr) { |
| 77 | ERR("Failed to parse session path \"%s\": couldn't identify hostname", path); |
| 78 | goto error; |
| 79 | } |
| 80 | |
| 81 | second_token_ptr = strtok_r(nullptr, "/", &leftover_ptr); |
| 82 | if (!second_token_ptr) { |
| 83 | ERR("Failed to parse session path \"%s\": couldn't identify session name", path); |
| 84 | goto error; |
| 85 | } |
| 86 | |
| 87 | /* |
| 88 | * Check if the second token is a base path set at url level. This is |
| 89 | * legal in streaming, live and snapshot [1]. Otherwise it is the |
| 90 | * session name with possibly a datetime attached [2]. Note that when |
| 91 | * "adding" snapshot output (lttng snapshot add-output), no session name |
| 92 | * is present in the path by default. The handling for "base path" take |
| 93 | * care of this case as well. |
| 94 | * [1] e.g --set-url net://localhost/my_marvellous_path |
| 95 | * [2] Can be: |
| 96 | * <session_name> |
| 97 | * When using --snapshot on session create. |
| 98 | * <session_name>-<date>-<time> |
| 99 | * <auto>-<date>-<time> |
| 100 | */ |
| 101 | if (strncmp(second_token_ptr, local_session_name, strlen(local_session_name)) != 0) { |
| 102 | /* |
| 103 | * Token does not start with session name. |
| 104 | * This mean this is an extra path scenario. |
| 105 | * Duplicate the current token since it is part of an |
| 106 | * base_path. |
| 107 | * Set secDuplicate the current token since it is part of an |
| 108 | * base_path. The rest is the leftover. |
| 109 | * Set second_token_ptr to the local_session_name for further |
| 110 | * processing. |
| 111 | */ |
| 112 | partial_base_path = strdup(second_token_ptr); |
| 113 | if (!partial_base_path) { |
| 114 | PERROR("Failed to parse session path: couldn't copy partial base path"); |
| 115 | goto error; |
| 116 | } |
| 117 | |
| 118 | second_token_ptr = local_session_name; |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | * Based on the previous test, we can move inside the token ptr to |
| 123 | * remove the "local_session_name" and inspect the rest of the token. |
| 124 | * We are looking into extracting the creation datetime from either the |
| 125 | * session_name or the token. We need to to all this gymnastic because |
| 126 | * an extra path could decide to append a datetime to its first |
| 127 | * subdirectory. |
| 128 | * Possible scenario: |
| 129 | * <session_name> |
| 130 | * <session_name>-<date>-<time> |
| 131 | * <auto>-<date>-<time> |
| 132 | * <session_name>_base_path_foo_bar |
| 133 | * <session_name>-<false date>-<false-time> (via a base path) |
| 134 | * |
| 135 | * We have no way to discern from the basic scenario of: |
| 136 | * <session_name>-<date>-<time> |
| 137 | * and one done using a base path with the exact format we normally |
| 138 | * expect. |
| 139 | * |
| 140 | * e.g: |
| 141 | * lttng create my_session -U |
| 142 | * net://localhost/my_session-19910319-120000/ |
| 143 | */ |
| 144 | ret = regcomp(®ex, DATETIME_REGEX, 0); |
| 145 | if (ret) { |
| 146 | ERR("Failed to parse session path: regex compilation failed with code %d", ret); |
| 147 | goto error; |
| 148 | } |
| 149 | |
| 150 | leftover_second_token_ptr = second_token_ptr + strlen(local_session_name); |
| 151 | len = strlen(leftover_second_token_ptr); |
| 152 | if (len == 0) { |
| 153 | /* |
| 154 | * We are either dealing with an auto session name or only the |
| 155 | * session_name. If this is a auto session name, we need to |
| 156 | * fetch the creation datetime. |
| 157 | */ |
| 158 | ret = regexec(®ex, local_session_name, 0, nullptr, 0); |
| 159 | if (ret == 0) { |
| 160 | const ssize_t local_session_name_offset = |
| 161 | strlen(local_session_name) - DATETIME_STR_LEN + 1; |
| 162 | |
| 163 | LTTNG_ASSERT(local_session_name_offset >= 0); |
| 164 | datetime = strdup(local_session_name + local_session_name_offset); |
| 165 | if (!datetime) { |
| 166 | PERROR("Failed to parse session path: couldn't copy datetime on regex match"); |
| 167 | goto error_regex; |
| 168 | } |
| 169 | } else { |
| 170 | datetime = calloc<char>(DATETIME_STR_LEN); |
| 171 | if (!datetime) { |
| 172 | PERROR("Failed to allocate DATETIME string"); |
| 173 | goto error; |
| 174 | } |
| 175 | |
| 176 | ret = time_to_datetime_str( |
| 177 | relay_session_creation_time, datetime, DATETIME_STR_LEN); |
| 178 | if (ret) { |
| 179 | /* time_to_datetime_str already logs errors. */ |
| 180 | goto error; |
| 181 | } |
| 182 | } |
| 183 | } else if (len == DATETIME_STR_LEN && |
| 184 | !regexec(®ex, leftover_second_token_ptr, 0, nullptr, 0)) { |
| 185 | /* |
| 186 | * The leftover from the second token is of format |
| 187 | * "-<datetime>", use it as the creation time. |
| 188 | * Ignore leading "-". |
| 189 | */ |
| 190 | datetime = strdup(&leftover_second_token_ptr[1]); |
| 191 | if (!datetime) { |
| 192 | PERROR("Failed to parse session path: couldn't copy datetime on regex match"); |
| 193 | goto error_regex; |
| 194 | } |
| 195 | } else { |
| 196 | /* |
| 197 | * Base path scenario. |
| 198 | * We cannot try to extract the datetime from the session name |
| 199 | * since nothing prevent a user to name a session in the |
| 200 | * "name-<datetime>" format. Using the datetime from such a |
| 201 | * session would be invalid. |
| 202 | * */ |
| 203 | LTTNG_ASSERT(partial_base_path == nullptr); |
| 204 | LTTNG_ASSERT(datetime == nullptr); |
| 205 | |
| 206 | partial_base_path = strdup(second_token_ptr); |
| 207 | if (!partial_base_path) { |
| 208 | PERROR("Failed to parse session path: couldn't copy partial base path"); |
| 209 | goto error_regex; |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | ret = asprintf(&filepath_per_session, |
| 214 | "%s/%s%s%s/%s%s%s", |
| 215 | local_session_name, |
| 216 | hostname_ptr, |
| 217 | datetime ? "-" : "", |
| 218 | datetime ? datetime : "", |
| 219 | partial_base_path ? partial_base_path : "", |
| 220 | partial_base_path ? "/" : "", |
| 221 | leftover_ptr); |
| 222 | if (ret < 0) { |
| 223 | filepath_per_session = nullptr; |
| 224 | goto error; |
| 225 | } |
| 226 | error_regex: |
| 227 | regfree(®ex); |
| 228 | error: |
| 229 | free(local_copy); |
| 230 | free(partial_base_path); |
| 231 | free(datetime); |
| 232 | return filepath_per_session; |
| 233 | } |