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