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