Fix: sink.ctf.fs: remove spurious directory level when using assume-single-trace
[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 BT_ASSERT(!trace->fs_sink->assume_single_trace);
397
398 /* First, try to build a path using environment fields written by LTTng. */
399 path = make_lttng_trace_path_rel(trace);
400 if (path) {
401 goto end;
402 }
403
404 /* Otherwise, use the trace name, if available. */
405 trace_name = bt_trace_get_name(trace->ir_trace);
406 if (trace_name) {
407 path = g_string_new(trace_name);
408 goto end;
409 }
410
411 /* Otherwise, use "trace". */
412 path = g_string_new("trace");
413
414 end:
415 return path;
416 }
417
418 /*
419 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
420 */
421
422 static
423 GString *make_trace_path(const struct fs_sink_trace *trace, const char *output_base_directory)
424 {
425 GString *rel_path = NULL;
426 GString *rel_path_san = NULL;
427 GString *full_path = NULL;
428 GString *unique_full_path = NULL;
429
430 if (trace->fs_sink->assume_single_trace) {
431 /* Use output directory directly */
432 unique_full_path = g_string_new(output_base_directory);
433 if (!unique_full_path) {
434 goto end;
435 }
436 } else {
437 rel_path = make_trace_path_rel(trace);
438 if (!rel_path) {
439 goto end;
440 }
441
442 rel_path_san = sanitize_trace_path(rel_path->str);
443 if (!rel_path_san) {
444 goto end;
445 }
446
447 full_path = g_string_new(NULL);
448 if (!full_path) {
449 goto end;
450 }
451
452 g_string_printf(full_path, "%s" G_DIR_SEPARATOR_S "%s",
453 output_base_directory, rel_path_san->str);
454
455 unique_full_path = make_unique_trace_path(full_path->str);
456 }
457
458 end:
459 if (rel_path) {
460 g_string_free(rel_path, TRUE);
461 }
462
463 if (rel_path_san) {
464 g_string_free(rel_path_san, TRUE);
465 }
466
467 if (full_path) {
468 g_string_free(full_path, TRUE);
469 }
470
471 return unique_full_path;
472 }
473
474 BT_HIDDEN
475 void fs_sink_trace_destroy(struct fs_sink_trace *trace)
476 {
477 GString *tsdl = NULL;
478 FILE *fh = NULL;
479 size_t len;
480
481 if (!trace) {
482 goto end;
483 }
484
485 if (trace->ir_trace_destruction_listener_id != UINT64_C(-1)) {
486 /*
487 * Remove the destruction listener, otherwise it could
488 * be called in the future, and its private data is this
489 * CTF FS sink trace object which won't exist anymore.
490 */
491 (void) bt_trace_remove_destruction_listener(trace->ir_trace,
492 trace->ir_trace_destruction_listener_id);
493 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
494 }
495
496 if (trace->streams) {
497 g_hash_table_destroy(trace->streams);
498 trace->streams = NULL;
499 }
500
501 tsdl = g_string_new(NULL);
502 BT_ASSERT(tsdl);
503 translate_trace_ctf_ir_to_tsdl(trace->trace, tsdl);
504
505 BT_ASSERT(trace->metadata_path);
506 fh = fopen(trace->metadata_path->str, "wb");
507 if (!fh) {
508 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
509 "cannot open metadata file for writing",
510 ": path=\"%s\"", trace->metadata_path->str);
511 bt_common_abort();
512 }
513
514 len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh);
515 if (len != tsdl->len) {
516 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
517 "cannot write metadata file",
518 ": path=\"%s\"", trace->metadata_path->str);
519 bt_common_abort();
520 }
521
522 if (!trace->fs_sink->quiet) {
523 printf("Created CTF trace `%s`.\n", trace->path->str);
524 }
525
526 if (trace->path) {
527 g_string_free(trace->path, TRUE);
528 trace->path = NULL;
529 }
530
531 if (fh) {
532 int ret = fclose(fh);
533
534 if (ret != 0) {
535 BT_COMP_LOGW_ERRNO("In trace destruction listener: "
536 "cannot close metadata file",
537 ": path=\"%s\"", trace->metadata_path->str);
538 }
539 }
540
541 g_string_free(trace->metadata_path, TRUE);
542 trace->metadata_path = NULL;
543
544 fs_sink_ctf_trace_destroy(trace->trace);
545 trace->trace = NULL;
546 g_free(trace);
547
548 g_string_free(tsdl, TRUE);
549
550 end:
551 return;
552 }
553
554 static
555 void ir_trace_destruction_listener(const bt_trace *ir_trace, void *data)
556 {
557 struct fs_sink_trace *trace = data;
558
559 /*
560 * Prevent bt_trace_remove_destruction_listener() from being
561 * called in fs_sink_trace_destroy(), which is called by
562 * g_hash_table_remove() below.
563 */
564 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
565 g_hash_table_remove(trace->fs_sink->traces, ir_trace);
566 }
567
568 BT_HIDDEN
569 struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink,
570 const bt_trace *ir_trace)
571 {
572 int ret;
573 struct fs_sink_trace *trace = g_new0(struct fs_sink_trace, 1);
574 bt_trace_add_listener_status trace_status;
575
576 if (!trace) {
577 goto end;
578 }
579
580 trace->log_level = fs_sink->log_level;
581 trace->fs_sink = fs_sink;
582 trace->ir_trace = ir_trace;
583 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
584 trace->trace = translate_trace_trace_ir_to_ctf_ir(fs_sink, ir_trace);
585 if (!trace->trace) {
586 goto error;
587 }
588
589 trace->path = make_trace_path(trace, fs_sink->output_dir_path->str);
590 BT_ASSERT(trace->path);
591 ret = g_mkdir_with_parents(trace->path->str, 0755);
592 if (ret) {
593 BT_COMP_LOGE_ERRNO("Cannot create directories for trace directory",
594 ": path=\"%s\"", trace->path->str);
595 goto error;
596 }
597
598 trace->metadata_path = g_string_new(trace->path->str);
599 BT_ASSERT(trace->metadata_path);
600 g_string_append(trace->metadata_path, "/metadata");
601 trace->streams = g_hash_table_new_full(g_direct_hash, g_direct_equal,
602 NULL, (GDestroyNotify) fs_sink_stream_destroy);
603 BT_ASSERT(trace->streams);
604 trace_status = bt_trace_add_destruction_listener(ir_trace,
605 ir_trace_destruction_listener, trace,
606 &trace->ir_trace_destruction_listener_id);
607 if (trace_status) {
608 goto error;
609 }
610
611 g_hash_table_insert(fs_sink->traces, (gpointer) ir_trace, trace);
612 goto end;
613
614 error:
615 fs_sink_trace_destroy(trace);
616 trace = NULL;
617
618 end:
619 return trace;
620 }
This page took 0.043212 seconds and 4 git commands to generate.