Move to kernel style SPDX license identifiers
[babeltrace.git] / src / plugins / ctf / fs-sink / fs-sink-trace.c
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
5 */
6
7 #define BT_COMP_LOG_SELF_COMP (trace->fs_sink->self_comp)
8 #define BT_LOG_OUTPUT_LEVEL (trace->log_level)
9 #define BT_LOG_TAG "PLUGIN/SINK.CTF.FS/TRACE"
10 #include "logging/comp-logging.h"
11
12 #include <babeltrace2/babeltrace.h>
13 #include <stdio.h>
14 #include <stdbool.h>
15 #include <glib.h>
16 #include "common/assert.h"
17 #include "ctfser/ctfser.h"
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 */
32 static
33 GString *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
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
104 static
105 GString *make_unique_trace_path(const char *path)
106 {
107 GString *unique_path;
108 unsigned int suffix = 0;
109
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
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
129 /*
130 * Validate that the input string `datetime` is an ISO8601-compliant string (the
131 * format used by LTTng in the metadata).
132 */
133
134 static
135 int lttng_validate_datetime(const struct fs_sink_trace *trace,
136 const char *datetime)
137 {
138 GTimeVal tv;
139 int ret = -1;
140
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)) {
148 BT_COMP_LOGI("Couldn't parse datetime as ISO 8601: date=\"%s\"", datetime);
149 goto end;
150 }
151
152 ret = 0;
153
154 end:
155 return ret;
156 }
157
158 #pragma GCC diagnostic pop
159
160 static
161 int append_lttng_trace_path_ust_uid(const struct fs_sink_trace *trace,
162 GString *path, const bt_trace *tc)
163 {
164 const bt_value *v;
165 int ret;
166
167 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "tracer_buffering_id");
168 if (!v || !bt_value_is_signed_integer(v)) {
169 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_buffering_id\"");
170 goto error;
171 }
172
173 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRId64,
174 bt_value_integer_signed_get(v));
175
176 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "architecture_bit_width");
177 if (!v || !bt_value_is_signed_integer(v)) {
178 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"architecture_bit_width\"");
179 goto error;
180 }
181
182 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRIu64 "-bit",
183 bt_value_integer_signed_get(v));
184
185 ret = 0;
186 goto end;
187
188 error:
189 ret = -1;
190
191 end:
192 return ret;
193 }
194
195 static
196 int append_lttng_trace_path_ust_pid(const struct fs_sink_trace *trace,
197 GString *path, const bt_trace *tc)
198 {
199 const bt_value *v;
200 const char *datetime;
201 int ret;
202
203 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "procname");
204 if (!v || !bt_value_is_string(v)) {
205 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"procname\"");
206 goto error;
207 }
208
209 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
210
211 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "vpid");
212 if (!v || !bt_value_is_signed_integer(v)) {
213 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"vpid\"");
214 goto error;
215 }
216
217 g_string_append_printf(path, "-%" PRId64, bt_value_integer_signed_get(v));
218
219 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "vpid_datetime");
220 if (!v || !bt_value_is_string(v)) {
221 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"vpid_datetime\"");
222 goto error;
223 }
224
225 datetime = bt_value_string_get(v);
226
227 if (lttng_validate_datetime(trace, datetime)) {
228 goto error;
229 }
230
231 g_string_append_printf(path, "-%s", datetime);
232
233 ret = 0;
234 goto end;
235
236 error:
237 ret = -1;
238
239 end:
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 */
247 static
248 GString *make_lttng_trace_path_rel(const struct fs_sink_trace *trace)
249 {
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
260 v = bt_trace_borrow_environment_entry_value_by_name_const(
261 trace->ir_trace, "tracer_name");
262 if (!v || !bt_value_is_string(v)) {
263 BT_COMP_LOGI_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_COMP_LOGI("Unrecognized tracer name: name=\"%s\"", tracer_name);
272 goto error;
273 }
274
275 v = bt_trace_borrow_environment_entry_value_by_name_const(
276 trace->ir_trace, "tracer_major");
277 if (!v || !bt_value_is_signed_integer(v)) {
278 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_major\"");
279 goto error;
280 }
281
282 tracer_major = bt_value_integer_signed_get(v);
283
284 v = bt_trace_borrow_environment_entry_value_by_name_const(
285 trace->ir_trace, "tracer_minor");
286 if (!v || !bt_value_is_signed_integer(v)) {
287 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_minor\"");
288 goto error;
289 }
290
291 tracer_minor = bt_value_integer_signed_get(v);
292
293 if (!(tracer_major >= 3 || (tracer_major == 2 && tracer_minor >= 11))) {
294 BT_COMP_LOGI("Unsupported LTTng version for automatic trace path: major=%" PRId64 ", minor=%" PRId64,
295 tracer_major, tracer_minor);
296 goto error;
297 }
298
299 v = bt_trace_borrow_environment_entry_value_by_name_const(
300 trace->ir_trace, "hostname");
301 if (!v || !bt_value_is_string(v)) {
302 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_hostname\"");
303 goto error;
304 }
305
306 g_string_assign(path, bt_value_string_get(v));
307
308 v = bt_trace_borrow_environment_entry_value_by_name_const(
309 trace->ir_trace, "trace_name");
310 if (!v || !bt_value_is_string(v)) {
311 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"trace_name\"");
312 goto error;
313 }
314
315 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
316
317 v = bt_trace_borrow_environment_entry_value_by_name_const(
318 trace->ir_trace, "trace_creation_datetime");
319 if (!v || !bt_value_is_string(v)) {
320 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"trace_creation_datetime\"");
321 goto error;
322 }
323
324 datetime = bt_value_string_get(v);
325
326 if (lttng_validate_datetime(trace, datetime)) {
327 goto error;
328 }
329
330 g_string_append_printf(path, "-%s", datetime);
331
332 v = bt_trace_borrow_environment_entry_value_by_name_const(
333 trace->ir_trace, "domain");
334 if (!v || !bt_value_is_string(v)) {
335 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"domain\"");
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
345 v = bt_trace_borrow_environment_entry_value_by_name_const(
346 trace->ir_trace, "tracer_buffering_scheme");
347 if (!v || !bt_value_is_string(v)) {
348 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_buffering_scheme\"");
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")) {
356 if (append_lttng_trace_path_ust_uid(trace, path,
357 trace->ir_trace)) {
358 goto error;
359 }
360 } else if (g_str_equal(tracer_buffering_scheme, "pid")){
361 if (append_lttng_trace_path_ust_pid(trace, path,
362 trace->ir_trace)) {
363 goto error;
364 }
365 } else {
366 /* Unknown buffering scheme. */
367 BT_COMP_LOGI("Unknown buffering scheme: tracer_buffering_scheme=\"%s\"", tracer_buffering_scheme);
368 goto error;
369 }
370 } else if (!g_str_equal(domain, "kernel")) {
371 /* Unknown domain. */
372 BT_COMP_LOGI("Unknown domain: domain=\"%s\"", domain);
373 goto error;
374 }
375
376 goto end;
377
378 error:
379 if (path) {
380 g_string_free(path, TRUE);
381 path = NULL;
382 }
383
384 end:
385 return path;
386 }
387
388 /* Build the relative output path for `trace`. */
389
390 static
391 GString *make_trace_path_rel(const struct fs_sink_trace *trace)
392 {
393 GString *path = NULL;
394 const char *trace_name;
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. */
409 trace_name = bt_trace_get_name(trace->ir_trace);
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
418 end:
419 return path;
420 }
421
422 /*
423 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
424 */
425
426 static
427 GString *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
454 end:
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
470 BT_HIDDEN
471 void 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);
499 translate_trace_ctf_ir_to_tsdl(trace->trace, tsdl);
500
501 BT_ASSERT(trace->metadata_path);
502 fh = fopen(trace->metadata_path->str, "wb");
503 if (!fh) {
504 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
505 "cannot open metadata file for writing",
506 ": path=\"%s\"", trace->metadata_path->str);
507 bt_common_abort();
508 }
509
510 len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh);
511 if (len != tsdl->len) {
512 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
513 "cannot write metadata file",
514 ": path=\"%s\"", trace->metadata_path->str);
515 bt_common_abort();
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
527 if (fh) {
528 int ret = fclose(fh);
529
530 if (ret != 0) {
531 BT_COMP_LOGW_ERRNO("In trace destruction listener: "
532 "cannot close metadata file",
533 ": path=\"%s\"", trace->metadata_path->str);
534 }
535 }
536
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
544 g_string_free(tsdl, TRUE);
545
546 end:
547 return;
548 }
549
550 static
551 void 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
564 BT_HIDDEN
565 struct 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);
570 bt_trace_add_listener_status trace_status;
571
572 if (!trace) {
573 goto end;
574 }
575
576 trace->log_level = fs_sink->log_level;
577 trace->fs_sink = fs_sink;
578 trace->ir_trace = ir_trace;
579 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
580 trace->trace = translate_trace_trace_ir_to_ctf_ir(fs_sink, ir_trace);
581 if (!trace->trace) {
582 goto error;
583 }
584
585 trace->path = make_trace_path(trace, fs_sink->output_dir_path->str);
586 BT_ASSERT(trace->path);
587 ret = g_mkdir_with_parents(trace->path->str, 0755);
588 if (ret) {
589 BT_COMP_LOGE_ERRNO("Cannot create directories for trace directory",
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
610 error:
611 fs_sink_trace_destroy(trace);
612 trace = NULL;
613
614 end:
615 return trace;
616 }
This page took 0.040996 seconds and 4 git commands to generate.