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