ctf: Use g_time_val_from_iso8601 instead of g_date_time_new_from_iso8601
[babeltrace.git] / plugins / ctf / fs-sink / fs-sink-trace.c
CommitLineData
46bdd3e0
PP
1/*
2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRACE"
24#include "logging.h"
25
26#include <babeltrace/babeltrace.h>
27#include <stdio.h>
28#include <stdbool.h>
29#include <glib.h>
30#include <babeltrace/assert-internal.h>
31#include <babeltrace/ctfser-internal.h>
32
33#include "translate-trace-ir-to-ctf-ir.h"
34#include "translate-ctf-ir-to-tsdl.h"
35#include "fs-sink.h"
36#include "fs-sink-trace.h"
37#include "fs-sink-stream.h"
38
39/*
40 * Sanitizes `path` so as to:
41 *
42 * * Replace `.` subdirectories with `_`.
43 * * Replace `..` subdirectories with `__`.
44 * * Remove trailing slashes.
45 */
46static
47GString *sanitize_trace_path(const char *path)
48{
49 GString *san_path = g_string_new(NULL);
50 const char *ch = path;
51 bool dir_start = true;
52
53 BT_ASSERT(san_path);
54 BT_ASSERT(path);
55
56 while (*ch != '\0') {
57 switch (*ch) {
58 case '/':
59 /* Start of directory */
60 dir_start = true;
61 g_string_append_c(san_path, *ch);
62 ch++;
63 continue;
64 case '.':
65 if (dir_start) {
66 switch (ch[1]) {
67 case '\0':
68 case '/':
69 /* `.` -> `_` */
70 g_string_append_c(san_path, '_');
71 ch++;
72 continue;
73 case '.':
74 switch (ch[2]) {
75 case '\0':
76 case '/':
77 /* `..` -> `__` */
78 g_string_append(san_path, "__");
79 ch += 2;
80 continue;
81 default:
82 break;
83 }
84 default:
85 break;
86 }
87 }
88 default:
89 break;
90 }
91
92 /* Not a special character */
93 g_string_append_c(san_path, *ch);
94 ch++;
95 dir_start = false;
96 }
97
98 /* Remove trailing slashes */
99 while (san_path->len > 0 &&
100 san_path->str[san_path->len - 1] == '/') {
101 /* Remove trailing slash */
102 g_string_set_size(san_path, san_path->len - 1);
103 }
104
105 if (san_path->len == 0) {
106 /* Looks like there's nothing left: just use `trace` */
107 g_string_assign(san_path, "trace");
108 }
109
110 return san_path;
111}
112
526ab5f0
SM
113/*
114 * Find a path based on `path` that doesn't exist yet. First, try `path`
115 * itself, then try with incrementing suffixes.
116 */
117
46bdd3e0 118static
526ab5f0 119GString *make_unique_trace_path(const char *path)
46bdd3e0 120{
526ab5f0 121 GString *unique_path;
46bdd3e0
PP
122 unsigned int suffix = 0;
123
526ab5f0
SM
124 unique_path = g_string_new(path);
125
126 while (g_file_test(unique_path->str, G_FILE_TEST_EXISTS)) {
127 g_string_printf(unique_path, "%s-%u", path, suffix);
128 suffix++;
129 }
130
131 return unique_path;
132}
133
134/*
135 * Validate that the input string `datetime` is an ISO8601-compliant string (the
136 * format used by LTTng in the metadata).
137 */
138
139static
140int lttng_validate_datetime(const char *datetime)
141{
cca91513 142 GTimeVal tv;
526ab5f0
SM
143 int ret = -1;
144
cca91513
SM
145 /*
146 * We are using g_time_val_from_iso8601, as the safer/more modern
147 * alternative, g_date_time_new_from_iso8601, is only available in
148 * glib >= 2.56, and this is sufficient for our use case of validating
149 * the format.
150 */
151 if (!g_time_val_from_iso8601(datetime, &tv)) {
526ab5f0
SM
152 BT_LOGD("Couldn't parse datetime as iso8601: date=\"%s\"", datetime);
153 goto end;
154 }
46bdd3e0 155
526ab5f0
SM
156 ret = 0;
157
158end:
526ab5f0
SM
159 return ret;
160}
161
162static
163int append_lttng_trace_path_ust_uid(GString *path, const bt_trace_class *tc)
164{
165 const bt_value *v;
166 int ret;
167
168 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "tracer_buffering_id");
169 if (!v || !bt_value_is_integer(v)) {
170 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_buffering_id\"");
171 goto error;
172 }
173
174 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRId64, bt_value_integer_get(v));
175
176 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "isa_length");
177 if (!v || !bt_value_is_integer(v)) {
178 BT_LOGD_STR("Couldn't get environment value: name=\"isa_length\"");
179 goto error;
180 }
181
182 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRIu64 "-bit", bt_value_integer_get(v));
183
184 ret = 0;
185 goto end;
186
187error:
188 ret = -1;
189
46bdd3e0 190end:
526ab5f0
SM
191 return ret;
192}
193
194static
195int append_lttng_trace_path_ust_pid(GString *path, const bt_trace_class *tc)
196{
197 const bt_value *v;
198 const char *datetime;
199 int ret;
200
201 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "procname");
202 if (!v || !bt_value_is_string(v)) {
203 BT_LOGD_STR("Couldn't get environment value: name=\"procname\"");
204 goto error;
205 }
206
207 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
208
209 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "vpid");
210 if (!v || !bt_value_is_integer(v)) {
211 BT_LOGD_STR("Couldn't get environment value: name=\"vpid\"");
212 goto error;
213 }
214
215 g_string_append_printf(path, "-%" PRId64, bt_value_integer_get(v));
216
217 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "vpid_datetime");
218 if (!v || !bt_value_is_string(v)) {
219 BT_LOGD_STR("Couldn't get environment value: name=\"vpid_datetime\"");
220 goto error;
221 }
222
223 datetime = bt_value_string_get(v);
224
225 if (lttng_validate_datetime(datetime)) {
226 goto error;
227 }
228
229 g_string_append_printf(path, "-%s", datetime);
230
231 ret = 0;
232 goto end;
233
234error:
235 ret = -1;
236
237end:
238 return ret;
239}
240
241/*
242 * Try to build a trace path based on environment values put in the trace
243 * environment by the LTTng tracer, starting with version 2.11.
244 */
245static
246GString *make_lttng_trace_path_rel(const struct fs_sink_trace *trace)
247{
248 const bt_trace_class *tc;
249 const bt_value *v;
250 const char *tracer_name, *domain, *datetime;
251 int64_t tracer_major, tracer_minor;
252 GString *path;
253
254 path = g_string_new(NULL);
255 if (!path) {
256 goto error;
257 }
258
259 tc = bt_trace_borrow_class_const(trace->ir_trace);
260
261 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "tracer_name");
262 if (!v || !bt_value_is_string(v)) {
263 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_name\"");
264 goto error;
265 }
266
267 tracer_name = bt_value_string_get(v);
268
269 if (!g_str_equal(tracer_name, "lttng-ust")
270 && !g_str_equal(tracer_name, "lttng-modules")) {
271 BT_LOGD("Unrecognized tracer name: name=\"%s\"", tracer_name);
272 goto error;
46bdd3e0
PP
273 }
274
526ab5f0
SM
275 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "tracer_major");
276 if (!v || !bt_value_is_integer(v)) {
277 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_major\"");
278 goto error;
279 }
280
281 tracer_major = bt_value_integer_get(v);
282
283 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "tracer_minor");
284 if (!v || !bt_value_is_integer(v)) {
285 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_minor\"");
286 goto error;
287 }
288
289 tracer_minor = bt_value_integer_get(v);
290
291 if (!(tracer_major >= 3 || (tracer_major == 2 && tracer_minor >= 11))) {
292 BT_LOGD("Unsupported LTTng version for automatic trace path: major=%" PRId64 ", minor=%" PRId64,
293 tracer_major, tracer_minor);
294 goto error;
295 }
296
297 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "hostname");
298 if (!v || !bt_value_is_string(v)) {
299 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_hostname\"");
300 goto error;
301 }
302
303 g_string_assign(path, bt_value_string_get(v));
304
305 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "trace_name");
306 if (!v || !bt_value_is_string(v)) {
307 BT_LOGD_STR("Couldn't get environment value: name=\"trace_name\"");
308 goto error;
309 }
310
311 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
312
313 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "trace_creation_datetime");
314 if (!v || !bt_value_is_string(v)) {
315 BT_LOGD_STR("Couldn't get environment value: name=\"trace_creation_datetime\"");
316 goto error;
317 }
318
319 datetime = bt_value_string_get(v);
320
321 if (lttng_validate_datetime(datetime)) {
322 goto error;
323 }
324
325 g_string_append_printf(path, "-%s", datetime);
326
327 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "domain");
328 if (!v || !bt_value_is_string(v)) {
329 BT_LOGD_STR("Couldn't get environment value: name=\"domain\"");
330 goto error;
331 }
332
333 domain = bt_value_string_get(v);
334 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", domain);
335
336 if (g_str_equal(domain, "ust")) {
337 const char *tracer_buffering_scheme;
338
339 v = bt_trace_class_borrow_environment_entry_value_by_name_const(tc, "tracer_buffering_scheme");
340 if (!v || !bt_value_is_string(v)) {
341 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_buffering_scheme\"");
342 goto error;
343 }
344
345 tracer_buffering_scheme = bt_value_string_get(v);
346 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", tracer_buffering_scheme);
347
348 if (g_str_equal(tracer_buffering_scheme, "uid")) {
349 if (append_lttng_trace_path_ust_uid(path, tc)) {
350 goto error;
351 }
352 } else if (g_str_equal(tracer_buffering_scheme, "pid")){
353 if (append_lttng_trace_path_ust_pid(path, tc)) {
354 goto error;
355 }
356 } else {
357 /* Unknown buffering scheme. */
358 BT_LOGD("Unknown buffering scheme: tracer_buffering_scheme=\"%s\"", tracer_buffering_scheme);
359 goto error;
360 }
361 } else if (!g_str_equal(domain, "kernel")) {
362 /* Unknown domain. */
363 BT_LOGD("Unknown domain: domain=\"%s\"", domain);
364 goto error;
365 }
366
367 goto end;
368
369error:
370 if (path) {
371 g_string_free(path, TRUE);
372 path = NULL;
373 }
374
375end:
376 return path;
377}
378
379/* Build the relative output path for `trace`. */
380
381static
382GString *make_trace_path_rel(const struct fs_sink_trace *trace)
383{
384 GString *path = NULL;
385
386 if (trace->fs_sink->assume_single_trace) {
387 /* Use output directory directly */
388 path = g_string_new("");
389 goto end;
390 }
391
392 /* First, try to build a path using environment fields written by LTTng. */
393 path = make_lttng_trace_path_rel(trace);
394 if (path) {
395 goto end;
396 }
397
398 /* Otherwise, use the trace name, if available. */
399 const char *trace_name = bt_trace_get_name(trace->ir_trace);
400 if (trace_name) {
401 path = g_string_new(trace_name);
402 goto end;
403 }
404
405 /* Otherwise, use "trace". */
406 path = g_string_new("trace");
407
408end:
46bdd3e0
PP
409 return path;
410}
411
526ab5f0
SM
412/*
413 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
414 */
415
416static
417GString *make_trace_path(const struct fs_sink_trace *trace, const char *output_base_directory)
418{
419 GString *rel_path = NULL;
420 GString *rel_path_san = NULL;
421 GString *full_path = NULL;
422 GString *unique_full_path = NULL;
423
424 rel_path = make_trace_path_rel(trace);
425 if (!rel_path) {
426 goto end;
427 }
428
429 rel_path_san = sanitize_trace_path(rel_path->str);
430 if (!rel_path_san) {
431 goto end;
432 }
433
434 full_path = g_string_new(NULL);
435 if (!full_path) {
436 goto end;
437 }
438
439 g_string_printf(full_path, "%s" G_DIR_SEPARATOR_S "%s",
440 output_base_directory, rel_path_san->str);
441
442 unique_full_path = make_unique_trace_path(full_path->str);
443
444end:
445 if (rel_path) {
446 g_string_free(rel_path, TRUE);
447 }
448
449 if (rel_path_san) {
450 g_string_free(rel_path_san, TRUE);
451 }
452
453 if (full_path) {
454 g_string_free(full_path, TRUE);
455 }
456
457 return unique_full_path;
458}
459
46bdd3e0
PP
460BT_HIDDEN
461void fs_sink_trace_destroy(struct fs_sink_trace *trace)
462{
463 GString *tsdl = NULL;
464 FILE *fh = NULL;
465 size_t len;
466
467 if (!trace) {
468 goto end;
469 }
470
471 if (trace->ir_trace_destruction_listener_id != UINT64_C(-1)) {
472 /*
473 * Remove the destruction listener, otherwise it could
474 * be called in the future, and its private data is this
475 * CTF FS sink trace object which won't exist anymore.
476 */
477 (void) bt_trace_remove_destruction_listener(trace->ir_trace,
478 trace->ir_trace_destruction_listener_id);
479 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
480 }
481
482 if (trace->streams) {
483 g_hash_table_destroy(trace->streams);
484 trace->streams = NULL;
485 }
486
487 tsdl = g_string_new(NULL);
488 BT_ASSERT(tsdl);
489 translate_trace_class_ctf_ir_to_tsdl(trace->tc, tsdl);
490 fh = fopen(trace->metadata_path->str, "wb");
491 if (!fh) {
492 BT_LOGF_ERRNO("In trace destruction listener: "
493 "cannot open metadata file for writing: ",
494 ": path=\"%s\"", trace->metadata_path->str);
495 abort();
496 }
497
498 len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh);
499 if (len != tsdl->len) {
500 BT_LOGF_ERRNO("In trace destruction listener: "
501 "cannot write metadata file: ",
502 ": path=\"%s\"", trace->metadata_path->str);
503 abort();
504 }
505
506 if (!trace->fs_sink->quiet) {
507 printf("Created CTF trace `%s`.\n", trace->path->str);
508 }
509
510 if (trace->path) {
511 g_string_free(trace->path, TRUE);
512 trace->path = NULL;
513 }
514
515 if (trace->metadata_path) {
516 g_string_free(trace->metadata_path, TRUE);
517 trace->metadata_path = NULL;
518 }
519
520 fs_sink_ctf_trace_class_destroy(trace->tc);
521 trace->tc = NULL;
522 g_free(trace);
523
524end:
525 if (fh) {
526 int ret = fclose(fh);
527
528 if (ret != 0) {
529 BT_LOGW_ERRNO("In trace destruction listener: "
530 "cannot close metadata file: ",
531 ": path=\"%s\"", trace->metadata_path->str);
532 }
533 }
534
535 if (tsdl) {
536 g_string_free(tsdl, TRUE);
537 }
538
539 return;
540}
541
542static
543void ir_trace_destruction_listener(const bt_trace *ir_trace, void *data)
544{
545 struct fs_sink_trace *trace = data;
546
547 /*
548 * Prevent bt_trace_remove_destruction_listener() from being
549 * called in fs_sink_trace_destroy(), which is called by
550 * g_hash_table_remove() below.
551 */
552 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
553 g_hash_table_remove(trace->fs_sink->traces, ir_trace);
554}
555
556BT_HIDDEN
557struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink,
558 const bt_trace *ir_trace)
559{
560 int ret;
561 struct fs_sink_trace *trace = g_new0(struct fs_sink_trace, 1);
46bdd3e0
PP
562 bt_trace_status trace_status;
563
564 if (!trace) {
565 goto end;
566 }
567
46bdd3e0
PP
568 trace->fs_sink = fs_sink;
569 trace->ir_trace = ir_trace;
570 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
571 trace->tc = translate_trace_class_trace_ir_to_ctf_ir(
572 bt_trace_borrow_class_const(ir_trace));
573 if (!trace->tc) {
574 goto error;
575 }
576
526ab5f0 577 trace->path = make_trace_path(trace, fs_sink->output_dir_path->str);
46bdd3e0
PP
578 BT_ASSERT(trace->path);
579 ret = g_mkdir_with_parents(trace->path->str, 0755);
580 if (ret) {
581 BT_LOGE_ERRNO("Cannot create directories for trace directory",
582 ": path=\"%s\"", trace->path->str);
583 goto error;
584 }
585
586 trace->metadata_path = g_string_new(trace->path->str);
587 BT_ASSERT(trace->metadata_path);
588 g_string_append(trace->metadata_path, "/metadata");
589 trace->streams = g_hash_table_new_full(g_direct_hash, g_direct_equal,
590 NULL, (GDestroyNotify) fs_sink_stream_destroy);
591 BT_ASSERT(trace->streams);
592 trace_status = bt_trace_add_destruction_listener(ir_trace,
593 ir_trace_destruction_listener, trace,
594 &trace->ir_trace_destruction_listener_id);
595 if (trace_status) {
596 goto error;
597 }
598
599 g_hash_table_insert(fs_sink->traces, (gpointer) ir_trace, trace);
600 goto end;
601
602error:
603 fs_sink_trace_destroy(trace);
604 trace = NULL;
605
606end:
607 return trace;
608}
This page took 0.047062 seconds and 4 git commands to generate.