Adapt `sink.ctf.fs` to current API
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 8 Mar 2019 16:06:38 +0000 (11:06 -0500)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 2 May 2019 04:09:19 +0000 (00:09 -0400)
This patch is a complete rewrite of the `sink.ctf.fs` component class to
work with the current library's API.

Changes
=======
A `sink.ctf.fs` component does not use the CTF writer API anymore: it
tracks its own traces and streams, and writes data thanks to the common,
internal ctfser API (also used by CTF writer).

On the metadata side, the component class has its own CTF IR data
structures. They are based on the common CTF source IR data structures,
but they have additional fields and otherwise contain only what's needed
for a `sink.ctf.fs` component to work.

A `sink.ctf.fs` component systematically "protects" structure FC member
and variant FC option names with a leading `_`. This is suggested by
TSDL 1.8. Because of this, any enumeration FC label could be what
selects a following variant FC option, so enumeration FC labels are also
escaped with `_`. This is a temporary measure which matches the
behaviour of `src.ctf.fs` and `src.ctf.lttng-live`. We should solve this
at the API level later.

If the component fails to resolve the length FC of a dynamic array FC or
the selector FC of a variant FC, it falls back to creating its own
length FC or selector FC just above the requesting FC, within the same
structure FC, giving it a name which avoids any clash with other
members. Here's an example (trace IR, as TSDL):

    struct {
        struct {
            integer { size = 8; } len;
        } s;
        string str[s.len];
    }

This layout is perfectly valid from the trace IR's point of view.
However it is known to fail in Babeltrace 1 and Trace Compass (but is
accepted by Babeltrace 2), so the actual TSDL output for this becomes:

    struct {
        struct {
            integer { size = 8; } _len;
        } _s;
        integer { size = 32; } ___str_len;
        string { encoding = UTF8; } _str[___str_len];
    }

The same goes for a variant FC's selector FC:

    struct {
        struct {
            enum : integer { size = 8; } {
                A = 1,
                B = 5,
                C = 17 ... 24
            } tag;
        } s;
        variant <s.tag> {
            integer { size = 8; } A;
            string B;
            struct { } C;
        } var;
    }

becomes:

    struct {
        struct {
            enum : integer { size = 8; } {
                "_A" = 1,
                "_B" = 5,
                "_C" = 17 ... 24,
            } _tag;
        } _s;
        enum : integer { size = 16; } {
            "_A" = 0,
            "_B" = 1,
            "_C" = 2,
        } ___var_tag;
        variant <___var_tag> {
            integer { size = 8; } _A;
            string { encoding = UTF8; } _B;
            struct { } _C;
        } _var;
    }

This way we don't lose the original length/selector field value while
still writing the dynamic array/variant field.

A generated dynamic array FC's length FC is a 32-bit unsigned integer FC
(should be enough) while a generated variant FC's selector FC is a
16-bit unsigned enumeration FC.

A `sink.ctf.fs` component writes its events to the appropriate streams
as it receives them, as opposed to CTF writer which flushes a whole
packet when calling bt_ctf_stream_flush(). This means a packet's total
size is not limited to the available memory.

A `sink.ctf.fs` component generates a new trace UUID instead of using
the original trace class's UUID. The logic behind this is that there is
no way to know that the received trace and streams match exactly the
original trace and streams (the source and filters can modify them), so
systematically using the original UUID could lead to two different CTF
traces having the same UUID.

A `sink.ctf.fs` component writes the metadata file of a given CTF trace
and closes it when the upstream message iterator ends or when the trace
is destroyed. The component does not take a trace or trace class
reference: it registers a trace destruction listener so that it can free
the resources associated to a trace (and its streams) as soon as it's
destroyed. Also, it frees the resouces associated to a given stream when
getting a stream end message for it.

The way output directory paths and stream file names are created is
unchanged from the previous version. The `assume-single-trace` parameter
still exists.

There are three new parameters:

`ignore-discarded-events` (boolean):
    Ignore discarded events messages. This can be useful because there
    are limitations (see below) regarding where discarded events
    messages can be in the message flow, and what their beginning and
    end times can be.

`ignore-discarded-packets` (boolean):
    Ignore discarded packets messages. This can be useful because there
    are limitations (see below) regarding where discarded packets
    messages can be in the message flow, and what their beginning and
    end times can be.

`quiet` (boolean):
    Do not print anything. When the component is not quiet, it prints a
    message to the standard output every time it creates a complete CTF
    trace with the absolute path to it.

Known limitations
=================
This component class is not complete, in that it does not support all
the features of Babeltrace 2. For most cases, there should be no problem
using it to write streams created by a `src.ctf.fs` or
`src.ctf.lttng-live` component.

As of this patch, the known limitations are:

* Only names which are valid in TSDL are supported. This applies to:

  * Trace class environment keys.
  * Structure FC member names.
  * Variant FC option names.
  * Clock class names.

* The value type of a trace class environment entry must be integer or
  string.

* Unknown stream clocks are not supported.

* Stream activity messages are ignored.

* Discarded events and packets messages must occur between packets.

* There must not be more than one discarded events message or more than
  one discarded packets message between packets.

* The time range of a discarded events message must be from the last
  packet's end time to the next packet's end time, except when it occurs
  before the first packet of a given stream, where its beginning time
  must be the first packet's beginning time.

* The time range of a discarded packets message must be from the last
  packet's end time to the next packet's beginning time.

All the constraints above are checked as soon as possible and, when one
is not satisfied, the component fails with a detailed error.

The very strict and very CTF-ish limitations regarding discarded events
and packets messages are the reason why I added the aforementioned
`ignore-discarded-events` and `ignore-discarded-packets` parameters.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
19 files changed:
include/babeltrace/ctfser-internal.h
plugins/ctf/Makefile.am
plugins/ctf/fs-sink/Makefile.am
plugins/ctf/fs-sink/fs-sink-ctf-meta.h [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink-stream.c [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink-stream.h [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink-trace.c [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink-trace.h [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink.c [new file with mode: 0644]
plugins/ctf/fs-sink/fs-sink.h [new file with mode: 0644]
plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.c [new file with mode: 0644]
plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.h [new file with mode: 0644]
plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.c [new file with mode: 0644]
plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.h [new file with mode: 0644]
plugins/ctf/fs-sink/write.c [deleted file]
plugins/ctf/fs-sink/writer.c [deleted file]
plugins/ctf/fs-sink/writer.h [deleted file]
plugins/ctf/fs-src/Makefile.am
plugins/ctf/plugin.c

index 8469a04e7bef8b9f05ea5e282a0d1ff9d2278d77..fdbd2290a9fe42532987514a06b2ded094acbbeb 100644 (file)
@@ -568,4 +568,10 @@ void bt_ctfser_set_offset_in_current_packet_bits(struct bt_ctfser *ctfser,
        ctfser->offset_in_cur_packet_bits = offset_bits;
 }
 
+static inline
+const char *bt_ctfser_get_file_path(struct bt_ctfser *ctfser)
+{
+       return ctfser->path->str;
+}
+
 #endif /* BABELTRACE_CTFSER_INTERNAL_H */
index e2141a27f8ba715364964dee7118d6da6bda33cc..de407fdae7bd09f8a7de7deeb9a8ac9980a8b194 100644 (file)
@@ -1,5 +1,5 @@
-SUBDIRS = common fs-src
-# fs-sink lttng-live
+SUBDIRS = common fs-src fs-sink
+# lttng-live
 
 noinst_HEADERS = print.h
 
@@ -14,15 +14,14 @@ babeltrace_plugin_ctf_la_LDFLAGS = \
        -avoid-version -module
 
 babeltrace_plugin_ctf_la_LIBADD = \
-       fs-src/libbabeltrace-plugin-ctf-fs.la \
-       common/libbabeltrace-plugin-ctf-common.la
-
-#      lttng-live/libbabeltrace-plugin-ctf-lttng-live.la \
-#      fs-sink/libbabeltrace-plugin-ctf-writer.la
+       fs-src/libbabeltrace-plugin-ctf-fs-src.la \
+       common/libbabeltrace-plugin-ctf-common.la \
+       fs-sink/libbabeltrace-plugin-ctf-fs-sink.la
 
 if !ENABLE_BUILT_IN_PLUGINS
 babeltrace_plugin_ctf_la_LIBADD += \
        $(top_builddir)/lib/libbabeltrace.la \
        $(top_builddir)/logging/libbabeltrace-logging.la \
-       $(top_builddir)/common/libbabeltrace-common.la
+       $(top_builddir)/common/libbabeltrace-common.la \
+       $(top_builddir)/ctfser/libbabeltrace-ctfser.la
 endif
index f60efc2dca6b1b7aec595cf52cafa05121a37723..7a33c8fa7a3403f846dbaf232856a2d428fe9e43 100644 (file)
@@ -1,13 +1,13 @@
-AM_CPPFLAGS += -I$(top_srcdir)/plugins \
-       -I$(top_srcdir)/plugins/libctfcopytrace
+noinst_LTLIBRARIES = libbabeltrace-plugin-ctf-fs-sink.la
 
-noinst_LTLIBRARIES = libbabeltrace-plugin-ctf-writer.la
-
-libbabeltrace_plugin_ctf_writer_la_LIBADD =
-libbabeltrace_plugin_ctf_writer_la_SOURCES = writer.c writer.h write.c \
-                                            logging.c logging.h
-
-if !ENABLE_BUILT_IN_PLUGINS
-libbabeltrace_plugin_ctf_writer_la_LIBADD += \
-       $(top_builddir)/plugins/libctfcopytrace/libctfcopytrace.la
-endif
+libbabeltrace_plugin_ctf_fs_sink_la_LIBADD =
+libbabeltrace_plugin_ctf_fs_sink_la_SOURCES = \
+       fs-sink.c \
+       fs-sink.h \
+       logging.c \
+       logging.h \
+       fs-sink-ctf-meta.h \
+       translate-trace-ir-to-ctf-ir.c \
+       translate-ctf-ir-to-tsdl.c \
+       fs-sink-stream.c \
+       fs-sink-trace.c
diff --git a/plugins/ctf/fs-sink/fs-sink-ctf-meta.h b/plugins/ctf/fs-sink/fs-sink-ctf-meta.h
new file mode 100644 (file)
index 0000000..657fdd7
--- /dev/null
@@ -0,0 +1,901 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_CTF_META_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_CTF_META_H
+
+/*
+ * Copyright 2018-2019 - Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ */
+
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/common-internal.h>
+#include <babeltrace/assert-internal.h>
+#include <babeltrace/compat/uuid-internal.h>
+#include <glib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+enum fs_sink_ctf_field_class_type {
+       FS_SINK_CTF_FIELD_CLASS_TYPE_INT,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_FLOAT,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_STRING,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE,
+       FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT,
+};
+
+struct fs_sink_ctf_field_class {
+       enum fs_sink_ctf_field_class_type type;
+
+       /* Weak */
+       const bt_field_class *ir_fc;
+
+       unsigned int alignment;
+
+       /* Index of the field class within its own parent */
+       uint64_t index_in_parent;
+};
+
+struct fs_sink_ctf_field_class_bit_array {
+       struct fs_sink_ctf_field_class base;
+       unsigned int size;
+};
+
+struct fs_sink_ctf_field_class_int {
+       struct fs_sink_ctf_field_class_bit_array base;
+       bool is_signed;
+};
+
+struct fs_sink_ctf_field_class_float {
+       struct fs_sink_ctf_field_class_bit_array base;
+};
+
+struct fs_sink_ctf_field_class_string {
+       struct fs_sink_ctf_field_class base;
+};
+
+struct fs_sink_ctf_named_field_class {
+       GString *name;
+
+       /* Owned by this */
+       struct fs_sink_ctf_field_class *fc;
+};
+
+struct fs_sink_ctf_field_class_struct {
+       struct fs_sink_ctf_field_class base;
+
+       /* Array of `struct fs_sink_ctf_named_field_class` */
+       GArray *members;
+};
+
+struct fs_sink_ctf_field_class_variant {
+       struct fs_sink_ctf_field_class base;
+       GString *tag_ref;
+       bool tag_is_before;
+
+       /* Array of `struct fs_sink_ctf_named_field_class` */
+       GArray *options;
+};
+
+struct fs_sink_ctf_field_class_array_base {
+       struct fs_sink_ctf_field_class base;
+       struct fs_sink_ctf_field_class *elem_fc;
+};
+
+struct fs_sink_ctf_field_class_array {
+       struct fs_sink_ctf_field_class_array_base base;
+       uint64_t length;
+};
+
+struct fs_sink_ctf_field_class_sequence {
+       struct fs_sink_ctf_field_class_array_base base;
+       GString *length_ref;
+       bool length_is_before;
+};
+
+struct fs_sink_ctf_stream_class;
+
+struct fs_sink_ctf_event_class {
+       /* Weak */
+       const bt_event_class *ir_ec;
+
+       /* Weak */
+       struct fs_sink_ctf_stream_class *sc;
+
+       /* Owned by this */
+       struct fs_sink_ctf_field_class *spec_context_fc;
+
+       /* Owned by this */
+       struct fs_sink_ctf_field_class *payload_fc;
+};
+
+struct fs_sink_ctf_trace_class;
+
+struct fs_sink_ctf_stream_class {
+       /* Weak */
+       struct fs_sink_ctf_trace_class *tc;
+
+       /* Weak */
+       const bt_stream_class *ir_sc;
+
+       /* Weak */
+       const bt_clock_class *default_clock_class;
+
+       GString *default_clock_class_name;
+
+       /* Owned by this */
+       struct fs_sink_ctf_field_class *packet_context_fc;
+
+       /* Owned by this */
+       struct fs_sink_ctf_field_class *event_common_context_fc;
+
+       /* Array of `struct fs_sink_ctf_event_class *` (owned by this) */
+       GPtrArray *event_classes;
+
+       /*
+        * `const bt_event_class *` (weak) ->
+        * `struct fs_sink_ctf_event_class *` (weak)
+        */
+       GHashTable *event_classes_from_ir;
+};
+
+struct fs_sink_ctf_trace_class {
+       /* Weak */
+       const bt_trace_class *ir_tc;
+
+       unsigned char uuid[BABELTRACE_UUID_LEN];
+
+       /* Array of `struct fs_sink_ctf_stream_class *` (owned by this) */
+       GPtrArray *stream_classes;
+};
+
+static inline
+void fs_sink_ctf_field_class_destroy(struct fs_sink_ctf_field_class *fc);
+
+static inline
+void _fs_sink_ctf_field_class_init(struct fs_sink_ctf_field_class *fc,
+               enum fs_sink_ctf_field_class_type type,
+               const bt_field_class *ir_fc, unsigned int alignment,
+               uint64_t index_in_parent)
+{
+       BT_ASSERT(fc);
+       fc->type = type;
+       fc->ir_fc = ir_fc;
+       fc->alignment = alignment;
+       fc->index_in_parent = index_in_parent;
+}
+
+static inline
+void _fs_sink_ctf_field_class_bit_array_init(
+               struct fs_sink_ctf_field_class_bit_array *fc,
+               enum fs_sink_ctf_field_class_type type,
+               const bt_field_class *ir_fc, unsigned int size,
+               uint64_t index_in_parent)
+{
+       _fs_sink_ctf_field_class_init((void *) fc, type, ir_fc,
+               size % 8 == 0 ? 8 : 1, index_in_parent);
+       fc->size = size;
+}
+
+static inline
+void _fs_sink_ctf_field_class_int_init(struct fs_sink_ctf_field_class_int *fc,
+               enum fs_sink_ctf_field_class_type type,
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       bt_field_class_type ir_fc_type = bt_field_class_get_type(ir_fc);
+
+       _fs_sink_ctf_field_class_bit_array_init((void *) fc, type, ir_fc,
+               (unsigned int) bt_field_class_integer_get_field_value_range(
+                       ir_fc),
+               index_in_parent);
+       fc->is_signed = (ir_fc_type == BT_FIELD_CLASS_TYPE_SIGNED_INTEGER ||
+               ir_fc_type == BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION);
+}
+
+static inline
+void _fs_sink_ctf_named_field_class_init(
+               struct fs_sink_ctf_named_field_class *named_fc)
+{
+       BT_ASSERT(named_fc);
+       named_fc->name = g_string_new(NULL);
+       BT_ASSERT(named_fc->name);
+}
+
+static inline
+void _fs_sink_ctf_named_field_class_fini(
+               struct fs_sink_ctf_named_field_class *named_fc)
+{
+       BT_ASSERT(named_fc);
+
+       if (named_fc->name) {
+               g_string_free(named_fc->name, TRUE);
+               named_fc->name = NULL;
+       }
+
+       fs_sink_ctf_field_class_destroy(named_fc->fc);
+       named_fc->fc = NULL;
+}
+
+static inline
+struct fs_sink_ctf_field_class_int *fs_sink_ctf_field_class_int_create(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_int *fc =
+               g_new0(struct fs_sink_ctf_field_class_int, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_int_init(fc, FS_SINK_CTF_FIELD_CLASS_TYPE_INT,
+               ir_fc, index_in_parent);
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_float *fs_sink_ctf_field_class_float_create(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_float *fc =
+               g_new0(struct fs_sink_ctf_field_class_float, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_bit_array_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_FLOAT,
+               ir_fc, bt_field_class_real_is_single_precision(ir_fc) ? 32 : 64,
+               index_in_parent);
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_string *fs_sink_ctf_field_class_string_create(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_string *fc =
+               g_new0(struct fs_sink_ctf_field_class_string, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_STRING, ir_fc,
+               8, index_in_parent);
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_struct *fs_sink_ctf_field_class_struct_create_empty(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_struct *fc =
+               g_new0(struct fs_sink_ctf_field_class_struct, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT, ir_fc, 1, index_in_parent);
+       fc->members = g_array_new(FALSE, TRUE,
+               sizeof(struct fs_sink_ctf_named_field_class));
+       BT_ASSERT(fc->members);
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_variant *fs_sink_ctf_field_class_variant_create_empty(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_variant *fc =
+               g_new0(struct fs_sink_ctf_field_class_variant, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT, ir_fc,
+               1, index_in_parent);
+       fc->options = g_array_new(FALSE, TRUE,
+               sizeof(struct fs_sink_ctf_named_field_class));
+       BT_ASSERT(fc->options);
+       fc->tag_ref = g_string_new(NULL);
+       BT_ASSERT(fc->tag_ref);
+       fc->tag_is_before =
+               bt_field_class_variant_borrow_selector_field_path_const(ir_fc) ==
+               NULL;
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_array *fs_sink_ctf_field_class_array_create_empty(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_array *fc =
+               g_new0(struct fs_sink_ctf_field_class_array, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY, ir_fc,
+               1, index_in_parent);
+       fc->length = bt_field_class_static_array_get_length(ir_fc);
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_sequence *fs_sink_ctf_field_class_sequence_create_empty(
+               const bt_field_class *ir_fc, uint64_t index_in_parent)
+{
+       struct fs_sink_ctf_field_class_sequence *fc =
+               g_new0(struct fs_sink_ctf_field_class_sequence, 1);
+
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_init((void *) fc,
+               FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE,
+               ir_fc, 1, index_in_parent);
+       fc->length_ref = g_string_new(NULL);
+       BT_ASSERT(fc->length_ref);
+       fc->length_is_before =
+               bt_field_class_dynamic_array_borrow_length_field_path_const(ir_fc) ==
+               NULL;
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_struct_borrow_member_by_index(
+               struct fs_sink_ctf_field_class_struct *fc, uint64_t index);
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_variant_borrow_option_by_index(
+               struct fs_sink_ctf_field_class_variant *fc, uint64_t index);
+
+static inline
+void _fs_sink_ctf_field_class_fini(struct fs_sink_ctf_field_class *fc)
+{
+       BT_ASSERT(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_int_destroy(
+               struct fs_sink_ctf_field_class_int *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_float_destroy(
+               struct fs_sink_ctf_field_class_float *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_string_destroy(
+               struct fs_sink_ctf_field_class_string *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_struct_destroy(
+               struct fs_sink_ctf_field_class_struct *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+
+       if (fc->members) {
+               uint64_t i;
+
+               for (i = 0; i < fc->members->len; i++) {
+                       struct fs_sink_ctf_named_field_class *named_fc =
+                               fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                                       fc, i);
+
+                       _fs_sink_ctf_named_field_class_fini(named_fc);
+               }
+
+               g_array_free(fc->members, TRUE);
+               fc->members = NULL;
+       }
+
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_array_base_fini(
+               struct fs_sink_ctf_field_class_array_base *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+       fs_sink_ctf_field_class_destroy(fc->elem_fc);
+       fc->elem_fc = NULL;
+}
+
+static inline
+void _fs_sink_ctf_field_class_array_destroy(
+               struct fs_sink_ctf_field_class_array *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_array_base_fini((void *) fc);
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_sequence_destroy(
+               struct fs_sink_ctf_field_class_sequence *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_array_base_fini((void *) fc);
+
+       if (fc->length_ref) {
+               g_string_free(fc->length_ref, TRUE);
+               fc->length_ref = NULL;
+       }
+
+       g_free(fc);
+}
+
+static inline
+void _fs_sink_ctf_field_class_variant_destroy(
+               struct fs_sink_ctf_field_class_variant *fc)
+{
+       BT_ASSERT(fc);
+       _fs_sink_ctf_field_class_fini((void *) fc);
+
+       if (fc->options) {
+               uint64_t i;
+
+               for (i = 0; i < fc->options->len; i++) {
+                       struct fs_sink_ctf_named_field_class *named_fc =
+                               fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                                       fc, i);
+
+                       _fs_sink_ctf_named_field_class_fini(named_fc);
+               }
+
+               g_array_free(fc->options, TRUE);
+               fc->options = NULL;
+       }
+
+       if (fc->tag_ref) {
+               g_string_free(fc->tag_ref, TRUE);
+               fc->tag_ref = NULL;
+       }
+
+       g_free(fc);
+}
+
+static inline
+void fs_sink_ctf_field_class_destroy(struct fs_sink_ctf_field_class *fc)
+{
+       if (!fc) {
+               return;
+       }
+
+       switch (fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_INT:
+               _fs_sink_ctf_field_class_int_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_FLOAT:
+               _fs_sink_ctf_field_class_float_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRING:
+               _fs_sink_ctf_field_class_string_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               _fs_sink_ctf_field_class_struct_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+               _fs_sink_ctf_field_class_array_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+               _fs_sink_ctf_field_class_sequence_destroy((void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+               _fs_sink_ctf_field_class_variant_destroy((void *) fc);
+               break;
+       default:
+               abort();
+       }
+}
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_struct_borrow_member_by_index(
+               struct fs_sink_ctf_field_class_struct *fc, uint64_t index)
+{
+       BT_ASSERT(fc);
+       BT_ASSERT(index < fc->members->len);
+       return &g_array_index(fc->members, struct fs_sink_ctf_named_field_class,
+               index);
+}
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_struct_borrow_member_by_name(
+               struct fs_sink_ctf_field_class_struct *fc, const char *name)
+{
+       uint64_t i;
+       struct fs_sink_ctf_named_field_class *ret_named_fc = NULL;
+
+       BT_ASSERT(fc);
+       BT_ASSERT(name);
+
+       for (i = 0; i < fc->members->len; i++) {
+               struct fs_sink_ctf_named_field_class *named_fc =
+                       fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                               fc, i);
+
+               if (strcmp(name, named_fc->name->str) == 0) {
+                       ret_named_fc = named_fc;
+                       goto end;
+               }
+       }
+
+end:
+       return ret_named_fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class *
+fs_sink_ctf_field_class_struct_borrow_member_field_class_by_name(
+               struct fs_sink_ctf_field_class_struct *struct_fc, const char *name)
+{
+       struct fs_sink_ctf_named_field_class *named_fc = NULL;
+       struct fs_sink_ctf_field_class *fc = NULL;
+
+       if (!struct_fc) {
+               goto end;
+       }
+
+       named_fc = fs_sink_ctf_field_class_struct_borrow_member_by_name(
+               struct_fc, name);
+       if (!named_fc) {
+               goto end;
+       }
+
+       fc = named_fc->fc;
+
+end:
+       return fc;
+}
+
+static inline
+struct fs_sink_ctf_field_class_int *
+fs_sink_ctf_field_class_struct_borrow_member_int_field_class_by_name(
+               struct fs_sink_ctf_field_class_struct *struct_fc,
+               const char *name)
+{
+       struct fs_sink_ctf_field_class_int *int_fc = NULL;
+
+       int_fc = (void *)
+               fs_sink_ctf_field_class_struct_borrow_member_field_class_by_name(
+                       struct_fc, name);
+       if (!int_fc) {
+               goto end;
+       }
+
+       if (int_fc->base.base.type != FS_SINK_CTF_FIELD_CLASS_TYPE_INT) {
+               int_fc = NULL;
+               goto end;
+       }
+
+end:
+       return int_fc;
+}
+
+static inline
+void fs_sink_ctf_field_class_struct_align_at_least(
+               struct fs_sink_ctf_field_class_struct *fc,
+               unsigned int alignment)
+{
+       if (alignment > fc->base.alignment) {
+               fc->base.alignment = alignment;
+       }
+}
+
+static inline
+void fs_sink_ctf_field_class_struct_append_member(
+               struct fs_sink_ctf_field_class_struct *fc,
+               const char *name, struct fs_sink_ctf_field_class *member_fc)
+{
+       struct fs_sink_ctf_named_field_class *named_fc;
+
+       BT_ASSERT(fc);
+       BT_ASSERT(name);
+       g_array_set_size(fc->members, fc->members->len + 1);
+
+       named_fc = &g_array_index(fc->members,
+               struct fs_sink_ctf_named_field_class, fc->members->len - 1);
+       _fs_sink_ctf_named_field_class_init(named_fc);
+       g_string_assign(named_fc->name, name);
+       named_fc->fc = member_fc;
+       fs_sink_ctf_field_class_struct_align_at_least(fc, member_fc->alignment);
+}
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_variant_borrow_option_by_index(
+               struct fs_sink_ctf_field_class_variant *fc, uint64_t index)
+{
+       BT_ASSERT(fc);
+       BT_ASSERT(index < fc->options->len);
+       return &g_array_index(fc->options, struct fs_sink_ctf_named_field_class,
+               index);
+}
+
+static inline
+struct fs_sink_ctf_named_field_class *
+fs_sink_ctf_field_class_variant_borrow_option_by_name(
+               struct fs_sink_ctf_field_class_variant *fc, const char *name)
+{
+       uint64_t i;
+       struct fs_sink_ctf_named_field_class *ret_named_fc = NULL;
+
+       BT_ASSERT(fc);
+       BT_ASSERT(name);
+
+       for (i = 0; i < fc->options->len; i++) {
+               struct fs_sink_ctf_named_field_class *named_fc =
+                       fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                               fc, i);
+
+               if (strcmp(name, named_fc->name->str) == 0) {
+                       ret_named_fc = named_fc;
+                       goto end;
+               }
+       }
+
+end:
+       return ret_named_fc;
+}
+
+static inline
+void fs_sink_ctf_field_class_variant_append_option(
+               struct fs_sink_ctf_field_class_variant *fc,
+               const char *name, struct fs_sink_ctf_field_class *option_fc)
+{
+       struct fs_sink_ctf_named_field_class *named_fc;
+
+       BT_ASSERT(fc);
+       BT_ASSERT(name);
+       g_array_set_size(fc->options, fc->options->len + 1);
+
+       named_fc = &g_array_index(fc->options,
+               struct fs_sink_ctf_named_field_class, fc->options->len - 1);
+       _fs_sink_ctf_named_field_class_init(named_fc);
+       g_string_assign(named_fc->name, name);
+       named_fc->fc = option_fc;
+}
+
+static inline
+struct fs_sink_ctf_event_class *fs_sink_ctf_event_class_create(
+               struct fs_sink_ctf_stream_class *sc,
+               const bt_event_class *ir_ec)
+{
+       struct fs_sink_ctf_event_class *ec =
+               g_new0(struct fs_sink_ctf_event_class, 1);
+
+       BT_ASSERT(sc);
+       BT_ASSERT(ir_ec);
+       BT_ASSERT(ec);
+       ec->ir_ec = ir_ec;
+       ec->sc = sc;
+       g_ptr_array_add(sc->event_classes, ec);
+       g_hash_table_insert(sc->event_classes_from_ir, (gpointer) ir_ec, ec);
+       return ec;
+}
+
+static inline
+void fs_sink_ctf_event_class_destroy(struct fs_sink_ctf_event_class *ec)
+{
+       if (!ec) {
+               return;
+       }
+
+       fs_sink_ctf_field_class_destroy(ec->spec_context_fc);
+       ec->spec_context_fc = NULL;
+       fs_sink_ctf_field_class_destroy(ec->payload_fc);
+       ec->payload_fc = NULL;
+       g_free(ec);
+}
+
+static inline
+struct fs_sink_ctf_stream_class *fs_sink_ctf_stream_class_create(
+               struct fs_sink_ctf_trace_class *tc,
+               const bt_stream_class *ir_sc)
+{
+       struct fs_sink_ctf_stream_class *sc =
+               g_new0(struct fs_sink_ctf_stream_class, 1);
+
+       BT_ASSERT(tc);
+       BT_ASSERT(ir_sc);
+       BT_ASSERT(sc);
+       sc->tc = tc;
+       sc->ir_sc = ir_sc;
+       sc->default_clock_class =
+               bt_stream_class_borrow_default_clock_class_const(ir_sc);
+       sc->default_clock_class_name = g_string_new(NULL);
+       BT_ASSERT(sc->default_clock_class_name);
+       sc->event_classes = g_ptr_array_new_with_free_func(
+               (GDestroyNotify) fs_sink_ctf_event_class_destroy);
+       BT_ASSERT(sc->event_classes);
+       sc->event_classes_from_ir = g_hash_table_new(g_direct_hash,
+               g_direct_equal);
+       BT_ASSERT(sc->event_classes_from_ir);
+       g_ptr_array_add(tc->stream_classes, sc);
+       return sc;
+}
+
+static inline
+void fs_sink_ctf_stream_class_destroy(struct fs_sink_ctf_stream_class *sc)
+{
+       if (!sc) {
+               return;
+       }
+
+       if (sc->default_clock_class_name) {
+               g_string_free(sc->default_clock_class_name, TRUE);
+               sc->default_clock_class_name = NULL;
+       }
+
+       if (sc->event_classes) {
+               g_ptr_array_free(sc->event_classes, TRUE);
+               sc->event_classes = NULL;
+       }
+
+       if (sc->event_classes_from_ir) {
+               g_hash_table_destroy(sc->event_classes_from_ir);
+               sc->event_classes_from_ir = NULL;
+       }
+
+       fs_sink_ctf_field_class_destroy(sc->packet_context_fc);
+       sc->packet_context_fc = NULL;
+       fs_sink_ctf_field_class_destroy(sc->event_common_context_fc);
+       sc->event_common_context_fc = NULL;
+       g_free(sc);
+}
+
+static inline
+void fs_sink_ctf_stream_class_append_event_class(
+               struct fs_sink_ctf_stream_class *sc,
+               struct fs_sink_ctf_event_class *ec)
+{
+       g_ptr_array_add(sc->event_classes, ec);
+}
+
+static inline
+void fs_sink_ctf_trace_class_destroy(struct fs_sink_ctf_trace_class *tc)
+{
+       if (!tc) {
+               return;
+       }
+
+       if (tc->stream_classes) {
+               g_ptr_array_free(tc->stream_classes, TRUE);
+               tc->stream_classes = NULL;
+       }
+
+       g_free(tc);
+}
+
+static inline
+struct fs_sink_ctf_trace_class *fs_sink_ctf_trace_class_create(
+               const bt_trace_class *ir_tc)
+{
+       struct fs_sink_ctf_trace_class *tc =
+               g_new0(struct fs_sink_ctf_trace_class, 1);
+
+       BT_ASSERT(tc);
+
+       if (bt_uuid_generate(tc->uuid)) {
+               fs_sink_ctf_trace_class_destroy(tc);
+               tc = NULL;
+               goto end;
+       }
+
+       tc->ir_tc = ir_tc;
+       tc->stream_classes = g_ptr_array_new_with_free_func(
+               (GDestroyNotify) fs_sink_ctf_stream_class_destroy);
+       BT_ASSERT(tc->stream_classes);
+
+end:
+       return tc;
+}
+
+static inline
+bool fs_sink_ctf_ist_valid_identifier(const char *name)
+{
+       const char *at;
+       uint64_t i;
+       bool ist_valid = true;
+       static const char *reserved_keywords[] = {
+               "align",
+               "callsite",
+               "const",
+               "char",
+               "clock",
+               "double",
+               "enum",
+               "env",
+               "event",
+               "floating_point",
+               "float",
+               "integer",
+               "int",
+               "long",
+               "short",
+               "signed",
+               "stream",
+               "string",
+               "struct",
+               "trace",
+               "typealias",
+               "typedef",
+               "unsigned",
+               "variant",
+               "void",
+               "_Bool",
+               "_Complex",
+               "_Imaginary",
+       };
+
+       /* Make sure the name is not a reserved keyword */
+       for (i = 0; i < sizeof(reserved_keywords) / sizeof(*reserved_keywords);
+                       i++) {
+               if (strcmp(name, reserved_keywords[i]) == 0) {
+                       ist_valid = false;
+                       goto end;
+               }
+       }
+
+       /* Make sure the name is not an empty string */
+       if (strlen(name) == 0) {
+               ist_valid = false;
+               goto end;
+       }
+
+       /* Make sure the name starts with a letter or `_` */
+       if (!isalpha(name[0]) && name[0] != '_') {
+               ist_valid = false;
+               goto end;
+       }
+
+       /* Make sure the name only contains letters, digits, and `_` */
+       for (at = name; *at != '\0'; at++) {
+               if (!isalnum(*at) && *at != '_') {
+                       ist_valid = false;
+                       goto end;
+               }
+       }
+
+end:
+       return ist_valid;
+}
+
+static inline
+int fs_sink_ctf_protect_name(GString *name)
+{
+       int ret = 0;
+
+       if (!fs_sink_ctf_ist_valid_identifier(name->str)) {
+               ret = -1;
+               goto end;
+       }
+
+       /* Prepend `_` to protect it */
+       g_string_prepend_c(name, '_');
+
+end:
+       return ret;
+}
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_CTF_META_H */
diff --git a/plugins/ctf/fs-sink/fs-sink-stream.c b/plugins/ctf/fs-sink/fs-sink-stream.c
new file mode 100644 (file)
index 0000000..b0b514a
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-STREAM"
+#include "logging.h"
+
+#include <babeltrace/babeltrace.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <babeltrace/assert-internal.h>
+#include <babeltrace/ctfser-internal.h>
+#include <babeltrace/endian-internal.h>
+
+#include "fs-sink-trace.h"
+#include "fs-sink-stream.h"
+#include "translate-trace-ir-to-ctf-ir.h"
+
+BT_HIDDEN
+void fs_sink_stream_destroy(struct fs_sink_stream *stream)
+{
+       if (!stream) {
+               goto end;
+       }
+
+       bt_ctfser_fini(&stream->ctfser);
+
+       if (stream->file_name) {
+               g_string_free(stream->file_name, TRUE);
+               stream->file_name = NULL;
+       }
+
+       bt_packet_put_ref(stream->packet_state.packet);
+       g_free(stream);
+
+end:
+       return;
+}
+
+static
+bool stream_file_name_exists(struct fs_sink_trace *trace, const char *name)
+{
+       bool exists = false;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init(&iter, trace->streams);
+
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               struct fs_sink_stream *stream = value;
+
+               if (strcmp(name, stream->file_name->str) == 0) {
+                       exists = true;
+                       goto end;
+               }
+       }
+
+end:
+       return exists;
+}
+
+static
+GString *sanitize_stream_file_name(const char *file_name)
+{
+       GString *san_file_name = g_string_new(NULL);
+       const char *ch;
+       gchar *basename;
+
+       BT_ASSERT(san_file_name);
+       BT_ASSERT(file_name);
+       basename = g_path_get_basename(file_name);
+
+       for (ch = basename; *ch != '\0'; ch++) {
+               if (*ch == '/') {
+                       g_string_append_c(san_file_name, '_');
+               } else {
+                       g_string_append_c(san_file_name, *ch);
+               }
+       }
+
+       /* Do not allow `.` and `..` either */
+       if (strcmp(san_file_name->str, ".") == 0 ||
+                       strcmp(san_file_name->str, "..") == 0) {
+               g_string_assign(san_file_name, "stream");
+       }
+
+       g_free(basename);
+       return san_file_name;
+}
+
+static
+GString *make_unique_stream_file_name(struct fs_sink_trace *trace,
+               const char *base)
+{
+       GString *san_base = sanitize_stream_file_name(base);
+       GString *name = g_string_new(san_base->str);
+       unsigned int suffix = 0;
+
+       BT_ASSERT(name);
+
+       while (stream_file_name_exists(trace, name->str) &&
+                       strcmp(name->str, "metadata") == 0) {
+               g_string_assign(name, san_base->str);
+               g_string_append_printf(name, "%u", suffix);
+               suffix++;
+       }
+
+       g_string_free(san_base, TRUE);
+       return name;
+}
+
+static
+void set_stream_file_name(struct fs_sink_stream *stream)
+{
+       const char *base_name = bt_stream_get_name(stream->ir_stream);
+
+       if (!base_name) {
+               base_name = "stream";
+       }
+
+       BT_ASSERT(!stream->file_name);
+       stream->file_name = make_unique_stream_file_name(stream->trace,
+               base_name);
+}
+
+BT_HIDDEN
+struct fs_sink_stream *fs_sink_stream_create(struct fs_sink_trace *trace,
+               const bt_stream *ir_stream)
+{
+       struct fs_sink_stream *stream = g_new0(struct fs_sink_stream, 1);
+       int ret;
+       GString *path = g_string_new(trace->path->str);
+
+       if (!stream) {
+               goto end;
+       }
+
+       stream->trace = trace;
+       stream->ir_stream = ir_stream;
+       stream->packet_state.beginning_cs = UINT64_C(-1);
+       stream->packet_state.end_cs = UINT64_C(-1);
+       stream->prev_packet_state.end_cs = UINT64_C(-1);
+       stream->prev_packet_state.discarded_events_counter = UINT64_C(-1);
+       stream->prev_packet_state.seq_num = UINT64_C(-1);
+       ret = try_translate_stream_class_trace_ir_to_ctf_ir(trace->tc,
+               bt_stream_borrow_class_const(ir_stream), &stream->sc);
+       if (ret) {
+               goto error;
+       }
+
+       set_stream_file_name(stream);
+       g_string_append_printf(path, "/%s", stream->file_name->str);
+       ret = bt_ctfser_init(&stream->ctfser, path->str);
+       if (ret) {
+               goto error;
+       }
+
+       g_hash_table_insert(trace->streams, (gpointer) ir_stream, stream);
+       goto end;
+
+error:
+       fs_sink_stream_destroy(stream);
+       stream = NULL;
+
+end:
+       if (path) {
+               g_string_free(path, TRUE);
+       }
+
+       return stream;
+}
+
+static
+int write_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class *fc, const bt_field *field);
+
+static inline
+int write_int_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_int *fc, const bt_field *field)
+{
+       int ret;
+
+       if (fc->is_signed) {
+               ret = bt_ctfser_write_signed_int(&stream->ctfser,
+                       bt_field_signed_integer_get_value(field),
+                       fc->base.base.alignment, fc->base.size, BYTE_ORDER);
+       } else {
+               ret = bt_ctfser_write_unsigned_int(&stream->ctfser,
+                       bt_field_unsigned_integer_get_value(field),
+                       fc->base.base.alignment, fc->base.size, BYTE_ORDER);
+       }
+
+       return ret;
+}
+
+static inline
+int write_float_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_float *fc, const bt_field *field)
+{
+       int ret;
+       double val = bt_field_real_get_value(field);
+
+       if (fc->base.size == 32) {
+               ret = bt_ctfser_write_float32(&stream->ctfser, val,
+                       fc->base.base.alignment, BYTE_ORDER);
+       } else {
+               ret = bt_ctfser_write_float32(&stream->ctfser, val,
+                       fc->base.base.alignment, BYTE_ORDER);
+       }
+
+       return ret;
+}
+
+static inline
+int write_string_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_string *fc, const bt_field *field)
+{
+       return bt_ctfser_write_string(&stream->ctfser,
+               bt_field_string_get_value(field));
+}
+
+static inline
+int write_array_field_elements(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_array_base *fc,
+               const bt_field *field)
+{
+       uint64_t i;
+       uint64_t len = bt_field_array_get_length(field);
+       int ret = 0;
+
+       for (i = 0; i < len; i++) {
+               const bt_field *elem_field =
+                       bt_field_array_borrow_element_field_by_index_const(
+                               field, i);
+               ret = write_field(stream, fc->elem_fc, elem_field);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+static inline
+int write_sequence_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_sequence *fc,
+               const bt_field *field)
+{
+       int ret;
+
+       if (fc->length_is_before) {
+               ret = bt_ctfser_write_unsigned_int(&stream->ctfser,
+                       bt_field_array_get_length(field), 8, 32, BYTE_ORDER);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+       ret = write_array_field_elements(stream, (void *) fc, field);
+
+end:
+       return ret;
+}
+
+static inline
+int write_struct_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_struct *fc,
+               const bt_field *field, bool align_struct)
+{
+       int ret = 0;
+       uint64_t i;
+
+       if (likely(align_struct)) {
+               ret = bt_ctfser_align_offset_in_current_packet(&stream->ctfser,
+                       fc->base.alignment);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+       for (i = 0; i < fc->members->len; i++) {
+               const bt_field *memb_field =
+                       bt_field_structure_borrow_member_field_by_index_const(
+                               field, i);
+               struct fs_sink_ctf_field_class *member_fc =
+                       fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                               fc, i)->fc;
+
+               ret = write_field(stream, member_fc, memb_field);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+static inline
+int write_variant_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class_variant *fc,
+               const bt_field *field)
+{
+       uint64_t opt_index =
+               bt_field_variant_get_selected_option_field_index(field);
+       int ret;
+
+       if (fc->tag_is_before) {
+               ret = bt_ctfser_write_unsigned_int(&stream->ctfser,
+                       opt_index, 8, 16, BYTE_ORDER);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+       ret = write_field(stream,
+               fs_sink_ctf_field_class_variant_borrow_option_by_index(fc,
+                       opt_index)->fc,
+               bt_field_variant_borrow_selected_option_field_const(field));
+
+end:
+       return ret;
+}
+
+static
+int write_field(struct fs_sink_stream *stream,
+               struct fs_sink_ctf_field_class *fc, const bt_field *field)
+{
+       int ret;
+
+       switch (fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_INT:
+               ret = write_int_field(stream, (void *) fc, field);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_FLOAT:
+               ret = write_float_field(stream, (void *) fc, field);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRING:
+               ret = write_string_field(stream, (void *) fc, field);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               ret = write_struct_field(stream, (void *) fc, field, true);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+               ret = write_array_field_elements(stream, (void *) fc, field);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+               ret = write_sequence_field(stream, (void *) fc, field);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+               ret = write_variant_field(stream, (void *) fc, field);
+               break;
+       default:
+               abort();
+       }
+
+       return ret;
+}
+
+static inline
+int write_event_header(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs, struct fs_sink_ctf_event_class *ec)
+{
+       int ret;
+
+       /* Event class ID */
+       ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               bt_event_class_get_id(ec->ir_ec), 8, 64, BYTE_ORDER);
+       if (unlikely(ret)) {
+               goto end;
+       }
+
+       /* Time */
+       if (stream->sc->default_clock_class) {
+               BT_ASSERT(cs);
+               ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+                       bt_clock_snapshot_get_value(cs), 8, 64, BYTE_ORDER);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+BT_HIDDEN
+int fs_sink_stream_write_event(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs, const bt_event *event,
+               struct fs_sink_ctf_event_class *ec)
+{
+       int ret;
+       const bt_field *field;
+
+       /* Header */
+       ret = write_event_header(stream, cs, ec);
+       if (unlikely(ret)) {
+               goto end;
+       }
+
+       /* Common context */
+       if (stream->sc->event_common_context_fc) {
+               field = bt_event_borrow_common_context_field_const(event);
+               BT_ASSERT(field);
+               ret = write_struct_field(stream,
+                       (void *) stream->sc->event_common_context_fc,
+                       field, true);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+       /* Specific context */
+       if (ec->spec_context_fc) {
+               field = bt_event_borrow_specific_context_field_const(event);
+               BT_ASSERT(field);
+               ret = write_struct_field(stream, (void *) ec->spec_context_fc,
+                       field, true);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+       /* Specific context */
+       if (ec->payload_fc) {
+               field = bt_event_borrow_payload_field_const(event);
+               BT_ASSERT(field);
+               ret = write_struct_field(stream, (void *) ec->payload_fc,
+                       field, true);
+               if (unlikely(ret)) {
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+static
+int write_packet_context(struct fs_sink_stream *stream)
+{
+       int ret;
+
+       /* Packet total size */
+       ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               stream->packet_state.total_size, 8, 64, BYTE_ORDER);
+       if (ret) {
+               goto end;
+       }
+
+       /* Packet content size */
+       ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               stream->packet_state.content_size, 8, 64, BYTE_ORDER);
+       if (ret) {
+               goto end;
+       }
+
+       if (stream->sc->default_clock_class) {
+               /* Beginning time */
+               ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+                       stream->packet_state.beginning_cs, 8, 64, BYTE_ORDER);
+               if (ret) {
+                       goto end;
+               }
+
+               /* End time */
+               ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+                       stream->packet_state.end_cs, 8, 64, BYTE_ORDER);
+               if (ret) {
+                       goto end;
+               }
+       }
+
+       /* Discarded event counter */
+       ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               stream->packet_state.discarded_events_counter, 8, 64,
+               BYTE_ORDER);
+       if (ret) {
+               goto end;
+       }
+
+       /* Sequence number */
+       ret = bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               stream->packet_state.seq_num, 8, 64, BYTE_ORDER);
+       if (ret) {
+               goto end;
+       }
+
+       /* Other members */
+       if (stream->sc->packet_context_fc) {
+               const bt_field *packet_context_field =
+                       bt_packet_borrow_context_field_const(
+                               stream->packet_state.packet);
+
+               BT_ASSERT(packet_context_field);
+               ret = write_struct_field(stream,
+                       (void *) stream->sc->packet_context_fc,
+                       packet_context_field, false);
+               if (ret) {
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+BT_HIDDEN
+int fs_sink_stream_open_packet(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs, const bt_packet *packet)
+{
+       int ret;
+       uint64_t i;
+
+       BT_ASSERT(!stream->packet_state.is_open);
+       bt_packet_put_ref(stream->packet_state.packet);
+       stream->packet_state.packet = packet;
+       bt_packet_get_ref(stream->packet_state.packet);
+       if (cs) {
+               stream->packet_state.beginning_cs =
+                       bt_clock_snapshot_get_value(cs);
+       }
+
+       /* Open packet */
+       ret = bt_ctfser_open_packet(&stream->ctfser);
+       if (ret) {
+               /* bt_ctfser_open_packet() logs errors */
+               goto end;
+       }
+
+       /* Packet header: magic */
+       bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               UINT64_C(0xc1fc1fc1), 8, 32, BYTE_ORDER);
+
+       /* Packet header: UUID */
+       for (i = 0; i < BABELTRACE_UUID_LEN; i++) {
+               bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+                       (uint64_t) stream->sc->tc->uuid[i], 8, 8, BYTE_ORDER);
+       }
+
+       /* Packet header: stream class ID */
+       bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               bt_stream_class_get_id(stream->sc->ir_sc), 8, 64, BYTE_ORDER);
+
+       /* Packet header: stream ID */
+       bt_ctfser_write_byte_aligned_unsigned_int(&stream->ctfser,
+               bt_stream_get_id(stream->ir_stream), 8, 64, BYTE_ORDER);
+
+       /* Save packet context's offset to rewrite it later */
+       stream->packet_state.context_offset_bits =
+               bt_ctfser_get_offset_in_current_packet_bits(&stream->ctfser);
+
+       /* Write packet context just to advance to content (first event) */
+       ret = write_packet_context(stream);
+       if (ret) {
+               goto end;
+       }
+
+       stream->packet_state.is_open = true;
+
+end:
+       return ret;
+}
+
+BT_HIDDEN
+int fs_sink_stream_close_packet(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs)
+{
+       int ret;
+
+       BT_ASSERT(stream->packet_state.is_open);
+
+       if (cs) {
+               stream->packet_state.end_cs = bt_clock_snapshot_get_value(cs);
+       }
+
+       stream->packet_state.content_size =
+               bt_ctfser_get_offset_in_current_packet_bits(&stream->ctfser);
+       stream->packet_state.total_size =
+               (stream->packet_state.content_size + 7) & ~UINT64_C(7);
+
+       /* Rewrite packet context */
+       bt_ctfser_set_offset_in_current_packet_bits(&stream->ctfser,
+               stream->packet_state.context_offset_bits);
+       ret = write_packet_context(stream);
+       if (ret) {
+               goto end;
+       }
+
+       /* Close packet */
+       bt_ctfser_close_current_packet(&stream->ctfser,
+               stream->packet_state.total_size / 8);
+
+       /* Partially copy current packet state to previous packet state */
+       stream->prev_packet_state.end_cs = stream->packet_state.end_cs;
+       stream->prev_packet_state.discarded_events_counter =
+               stream->packet_state.discarded_events_counter;
+       stream->prev_packet_state.seq_num =
+               stream->packet_state.seq_num;
+
+       /* Reset current packet state */
+       stream->packet_state.beginning_cs = UINT64_C(-1);
+       stream->packet_state.end_cs = UINT64_C(-1);
+       stream->packet_state.content_size = 0;
+       stream->packet_state.total_size = 0;
+       stream->packet_state.seq_num += 1;
+       stream->packet_state.context_offset_bits = 0;
+       stream->packet_state.is_open = false;
+       BT_PACKET_PUT_REF_AND_RESET(stream->packet_state.packet);
+
+end:
+       return ret;
+}
diff --git a/plugins/ctf/fs-sink/fs-sink-stream.h b/plugins/ctf/fs-sink/fs-sink-stream.h
new file mode 100644 (file)
index 0000000..9f48ce8
--- /dev/null
@@ -0,0 +1,104 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_STREAM_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_STREAM_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/ctfser-internal.h>
+#include <glib.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "fs-sink-ctf-meta.h"
+
+struct fs_sink_trace;
+
+struct fs_sink_stream {
+       struct fs_sink_trace *trace;
+       struct bt_ctfser ctfser;
+
+       /* Stream's file name */
+       GString *file_name;
+
+       /* Weak */
+       const bt_stream *ir_stream;
+
+       struct fs_sink_ctf_stream_class *sc;
+
+       struct {
+               bool is_open;
+               uint64_t beginning_cs;
+               uint64_t end_cs;
+               uint64_t content_size;
+               uint64_t total_size;
+               uint64_t discarded_events_counter;
+               uint64_t seq_num;
+               uint64_t context_offset_bits;
+
+               /* Owned by this */
+               const bt_packet *packet;
+       } packet_state;
+
+       struct {
+               uint64_t end_cs;
+               uint64_t discarded_events_counter;
+               uint64_t seq_num;
+       } prev_packet_state;
+
+       struct {
+               bool in_range;
+               uint64_t beginning_cs;
+               uint64_t end_cs;
+       } discarded_events_state;
+
+       struct {
+               bool in_range;
+               uint64_t beginning_cs;
+               uint64_t end_cs;
+       } discarded_packets_state;
+
+       bool in_discarded_events_range;
+};
+
+BT_HIDDEN
+struct fs_sink_stream *fs_sink_stream_create(struct fs_sink_trace *trace,
+               const bt_stream *ir_stream);
+
+BT_HIDDEN
+void fs_sink_stream_destroy(struct fs_sink_stream *stream);
+
+BT_HIDDEN
+int fs_sink_stream_write_event(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs, const bt_event *event,
+               struct fs_sink_ctf_event_class *ec);
+
+BT_HIDDEN
+int fs_sink_stream_open_packet(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs, const bt_packet *packet);
+
+BT_HIDDEN
+int fs_sink_stream_close_packet(struct fs_sink_stream *stream,
+               const bt_clock_snapshot *cs);
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_STREAM_H */
diff --git a/plugins/ctf/fs-sink/fs-sink-trace.c b/plugins/ctf/fs-sink/fs-sink-trace.c
new file mode 100644 (file)
index 0000000..1eb87b5
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRACE"
+#include "logging.h"
+
+#include <babeltrace/babeltrace.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <babeltrace/assert-internal.h>
+#include <babeltrace/ctfser-internal.h>
+
+#include "translate-trace-ir-to-ctf-ir.h"
+#include "translate-ctf-ir-to-tsdl.h"
+#include "fs-sink.h"
+#include "fs-sink-trace.h"
+#include "fs-sink-stream.h"
+
+/*
+ * Sanitizes `path` so as to:
+ *
+ * * Replace `.` subdirectories with `_`.
+ * * Replace `..` subdirectories with `__`.
+ * * Remove trailing slashes.
+ */
+static
+GString *sanitize_trace_path(const char *path)
+{
+       GString *san_path = g_string_new(NULL);
+       const char *ch = path;
+       bool dir_start = true;
+
+       BT_ASSERT(san_path);
+       BT_ASSERT(path);
+
+       while (*ch != '\0') {
+               switch (*ch) {
+               case '/':
+                       /* Start of directory */
+                       dir_start = true;
+                       g_string_append_c(san_path, *ch);
+                       ch++;
+                       continue;
+               case '.':
+                       if (dir_start) {
+                               switch (ch[1]) {
+                               case '\0':
+                               case '/':
+                                       /* `.` -> `_` */
+                                       g_string_append_c(san_path, '_');
+                                       ch++;
+                                       continue;
+                               case '.':
+                                       switch (ch[2]) {
+                                       case '\0':
+                                       case '/':
+                                               /* `..` -> `__` */
+                                               g_string_append(san_path, "__");
+                                               ch += 2;
+                                               continue;
+                                       default:
+                                               break;
+                                       }
+                               default:
+                                       break;
+                               }
+                       }
+               default:
+                       break;
+               }
+
+               /* Not a special character */
+               g_string_append_c(san_path, *ch);
+               ch++;
+               dir_start = false;
+       }
+
+       /* Remove trailing slashes */
+       while (san_path->len > 0 &&
+                       san_path->str[san_path->len - 1] == '/') {
+               /* Remove trailing slash */
+               g_string_set_size(san_path, san_path->len - 1);
+       }
+
+       if (san_path->len == 0) {
+               /* Looks like there's nothing left: just use `trace` */
+               g_string_assign(san_path, "trace");
+       }
+
+       return san_path;
+}
+
+static
+GString *make_unique_trace_path(struct fs_sink_comp *fs_sink,
+               const char *output_dir_path, const char *base)
+{
+       GString *path = g_string_new(output_dir_path);
+       GString *san_base = NULL;
+       unsigned int suffix = 0;
+
+       if (fs_sink->assume_single_trace) {
+               /* Use output directory directly */
+               goto end;
+       }
+
+       san_base = sanitize_trace_path(base);
+       g_string_append_printf(path, "/%s", san_base->str);
+
+       while (g_file_test(path->str, G_FILE_TEST_IS_DIR)) {
+               g_string_assign(path, output_dir_path);
+               g_string_append_printf(path, "/%s%u", san_base->str, suffix);
+               suffix++;
+       }
+
+end:
+       if (san_base) {
+               g_string_free(san_base, TRUE);
+       }
+
+       return path;
+}
+
+BT_HIDDEN
+void fs_sink_trace_destroy(struct fs_sink_trace *trace)
+{
+       GString *tsdl = NULL;
+       FILE *fh = NULL;
+       size_t len;
+
+       if (!trace) {
+               goto end;
+       }
+
+       if (trace->ir_trace_destruction_listener_id != UINT64_C(-1)) {
+               /*
+                * Remove the destruction listener, otherwise it could
+                * be called in the future, and its private data is this
+                * CTF FS sink trace object which won't exist anymore.
+                */
+               (void) bt_trace_remove_destruction_listener(trace->ir_trace,
+                       trace->ir_trace_destruction_listener_id);
+               trace->ir_trace_destruction_listener_id = UINT64_C(-1);
+       }
+
+       if (trace->streams) {
+               g_hash_table_destroy(trace->streams);
+               trace->streams = NULL;
+       }
+
+       tsdl = g_string_new(NULL);
+       BT_ASSERT(tsdl);
+       translate_trace_class_ctf_ir_to_tsdl(trace->tc, tsdl);
+       fh = fopen(trace->metadata_path->str, "wb");
+       if (!fh) {
+               BT_LOGF_ERRNO("In trace destruction listener: "
+                       "cannot open metadata file for writing: ",
+                       ": path=\"%s\"", trace->metadata_path->str);
+               abort();
+       }
+
+       len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh);
+       if (len != tsdl->len) {
+               BT_LOGF_ERRNO("In trace destruction listener: "
+                       "cannot write metadata file: ",
+                       ": path=\"%s\"", trace->metadata_path->str);
+               abort();
+       }
+
+       if (!trace->fs_sink->quiet) {
+               printf("Created CTF trace `%s`.\n", trace->path->str);
+       }
+
+       if (trace->path) {
+               g_string_free(trace->path, TRUE);
+               trace->path = NULL;
+       }
+
+       if (trace->metadata_path) {
+               g_string_free(trace->metadata_path, TRUE);
+               trace->metadata_path = NULL;
+       }
+
+       fs_sink_ctf_trace_class_destroy(trace->tc);
+       trace->tc = NULL;
+       g_free(trace);
+
+end:
+       if (fh) {
+               int ret = fclose(fh);
+
+               if (ret != 0) {
+                       BT_LOGW_ERRNO("In trace destruction listener: "
+                               "cannot close metadata file: ",
+                               ": path=\"%s\"", trace->metadata_path->str);
+               }
+       }
+
+       if (tsdl) {
+               g_string_free(tsdl, TRUE);
+       }
+
+       return;
+}
+
+static
+void ir_trace_destruction_listener(const bt_trace *ir_trace, void *data)
+{
+       struct fs_sink_trace *trace = data;
+
+       /*
+        * Prevent bt_trace_remove_destruction_listener() from being
+        * called in fs_sink_trace_destroy(), which is called by
+        * g_hash_table_remove() below.
+        */
+       trace->ir_trace_destruction_listener_id = UINT64_C(-1);
+       g_hash_table_remove(trace->fs_sink->traces, ir_trace);
+}
+
+BT_HIDDEN
+struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink,
+               const bt_trace *ir_trace)
+{
+       int ret;
+       struct fs_sink_trace *trace = g_new0(struct fs_sink_trace, 1);
+       const char *trace_name = bt_trace_get_name(ir_trace);
+       bt_trace_status trace_status;
+
+       if (!trace) {
+               goto end;
+       }
+
+       if (!trace_name) {
+               trace_name = "trace";
+       }
+
+       trace->fs_sink = fs_sink;
+       trace->ir_trace = ir_trace;
+       trace->ir_trace_destruction_listener_id = UINT64_C(-1);
+       trace->tc = translate_trace_class_trace_ir_to_ctf_ir(
+               bt_trace_borrow_class_const(ir_trace));
+       if (!trace->tc) {
+               goto error;
+       }
+
+       trace->path = make_unique_trace_path(fs_sink,
+               fs_sink->output_dir_path->str, trace_name);
+       BT_ASSERT(trace->path);
+       ret = g_mkdir_with_parents(trace->path->str, 0755);
+       if (ret) {
+               BT_LOGE_ERRNO("Cannot create directories for trace directory",
+                       ": path=\"%s\"", trace->path->str);
+               goto error;
+       }
+
+       trace->metadata_path = g_string_new(trace->path->str);
+       BT_ASSERT(trace->metadata_path);
+       g_string_append(trace->metadata_path, "/metadata");
+       trace->streams = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+               NULL, (GDestroyNotify) fs_sink_stream_destroy);
+       BT_ASSERT(trace->streams);
+       trace_status = bt_trace_add_destruction_listener(ir_trace,
+               ir_trace_destruction_listener, trace,
+               &trace->ir_trace_destruction_listener_id);
+       if (trace_status) {
+               goto error;
+       }
+
+       g_hash_table_insert(fs_sink->traces, (gpointer) ir_trace, trace);
+       goto end;
+
+error:
+       fs_sink_trace_destroy(trace);
+       trace = NULL;
+
+end:
+       return trace;
+}
diff --git a/plugins/ctf/fs-sink/fs-sink-trace.h b/plugins/ctf/fs-sink/fs-sink-trace.h
new file mode 100644 (file)
index 0000000..b95e6b1
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_TRACE_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_TRACE_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/ctfser-internal.h>
+#include <glib.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "fs-sink-ctf-meta.h"
+
+struct fs_sink_comp;
+
+struct fs_sink_trace {
+       struct fs_sink_comp *fs_sink;
+
+       /* Owned by this */
+       struct fs_sink_ctf_trace_class *tc;
+
+       /*
+        * Weak reference: this object does not own it, and `tc` above
+        * does not own its trace IR trace class either. Instead, we add
+        * a "trace destruction" listener (in create_trace()) so that
+        * this object gets destroyed when the trace object is
+        * destroyed.
+        *
+        * Otherwise (with a strong reference), we would keep this trace
+        * object alive until the upstream message iterator ends. This
+        * could "leak" resources (memory, file descriptors) associated
+        * to traces and streams which otherwise would not exist.
+        */
+       const bt_trace *ir_trace;
+
+       uint64_t ir_trace_destruction_listener_id;
+
+       /* Trace's directory */
+       GString *path;
+
+       /* `metadata` file path */
+       GString *metadata_path;
+
+       /*
+        * Hash table of `const bt_stream *` (weak) to
+        * `struct fs_sink_stream *` (owned by hash table).
+        */
+       GHashTable *streams;
+};
+
+BT_HIDDEN
+struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink,
+               const bt_trace *ir_trace);
+
+BT_HIDDEN
+void fs_sink_trace_destroy(struct fs_sink_trace *trace);
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_TRACE_H */
diff --git a/plugins/ctf/fs-sink/fs-sink.c b/plugins/ctf/fs-sink/fs-sink.c
new file mode 100644 (file)
index 0000000..bc8d3fa
--- /dev/null
@@ -0,0 +1,903 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK"
+#include "logging.h"
+
+#include <babeltrace/babeltrace.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <babeltrace/assert-internal.h>
+#include <babeltrace/ctfser-internal.h>
+
+#include "fs-sink.h"
+#include "fs-sink-trace.h"
+#include "fs-sink-stream.h"
+#include "fs-sink-ctf-meta.h"
+#include "translate-trace-ir-to-ctf-ir.h"
+#include "translate-ctf-ir-to-tsdl.h"
+
+static
+const char * const in_port_name = "in";
+
+static
+bt_self_component_status ensure_output_dir_exists(
+               struct fs_sink_comp *fs_sink)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       int ret;
+
+       ret = g_mkdir_with_parents(fs_sink->output_dir_path->str, 0755);
+       if (ret) {
+               BT_LOGE_ERRNO("Cannot create directories for output directory",
+                       ": output-dir-path=\"%s\"",
+                       fs_sink->output_dir_path->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+end:
+       return status;
+}
+
+static
+bt_self_component_status configure_component(struct fs_sink_comp *fs_sink,
+               const bt_value *params)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_value *value;
+
+       value = bt_value_map_borrow_entry_value_const(params, "path");
+       if (!value) {
+               BT_LOGE_STR("Missing mandatory `path` parameter.");
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (!bt_value_is_string(value)) {
+               BT_LOGE_STR("`path` parameter: expecting a string.");
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       g_string_assign(fs_sink->output_dir_path,
+               bt_value_string_get(value));
+       value = bt_value_map_borrow_entry_value_const(params,
+               "assume-single-trace");
+       if (value) {
+               if (!bt_value_is_bool(value)) {
+                       BT_LOGE_STR("`assume-single-trace` parameter: expecting a boolean.");
+                       status = BT_SELF_COMPONENT_STATUS_ERROR;
+                       goto end;
+               }
+
+               fs_sink->assume_single_trace = (bool) bt_value_bool_get(value);
+       }
+
+       value = bt_value_map_borrow_entry_value_const(params,
+               "ignore-discarded-events");
+       if (value) {
+               if (!bt_value_is_bool(value)) {
+                       BT_LOGE_STR("`ignore-discarded-events` parameter: expecting a boolean.");
+                       status = BT_SELF_COMPONENT_STATUS_ERROR;
+                       goto end;
+               }
+
+               fs_sink->ignore_discarded_events =
+                       (bool) bt_value_bool_get(value);
+       }
+
+       value = bt_value_map_borrow_entry_value_const(params,
+               "ignore-discarded-packets");
+       if (value) {
+               if (!bt_value_is_bool(value)) {
+                       BT_LOGE_STR("`ignore-discarded-packets` parameter: expecting a boolean.");
+                       status = BT_SELF_COMPONENT_STATUS_ERROR;
+                       goto end;
+               }
+
+               fs_sink->ignore_discarded_packets =
+                       (bool) bt_value_bool_get(value);
+       }
+
+       value = bt_value_map_borrow_entry_value_const(params,
+               "quiet");
+       if (value) {
+               if (!bt_value_is_bool(value)) {
+                       BT_LOGE_STR("`quiet` parameter: expecting a boolean.");
+                       status = BT_SELF_COMPONENT_STATUS_ERROR;
+                       goto end;
+               }
+
+               fs_sink->quiet = (bool) bt_value_bool_get(value);
+       }
+
+end:
+       return status;
+}
+
+static
+void destroy_fs_sink_comp(struct fs_sink_comp *fs_sink)
+{
+       if (!fs_sink) {
+               goto end;
+       }
+
+       if (fs_sink->output_dir_path) {
+               g_string_free(fs_sink->output_dir_path, TRUE);
+               fs_sink->output_dir_path = NULL;
+       }
+
+       if (fs_sink->traces) {
+               g_hash_table_destroy(fs_sink->traces);
+               fs_sink->traces = NULL;
+       }
+
+       BT_SELF_COMPONENT_PORT_INPUT_MESSAGE_ITERATOR_PUT_REF_AND_RESET(
+               fs_sink->upstream_iter);
+       g_free(fs_sink);
+
+end:
+       return;
+}
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_init(
+               bt_self_component_sink *self_comp, const bt_value *params,
+               void *init_method_data)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       struct fs_sink_comp *fs_sink = NULL;
+
+       fs_sink = g_new0(struct fs_sink_comp, 1);
+       if (!fs_sink) {
+               BT_LOGE_STR("Failed to allocate one CTF FS sink structure.");
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto end;
+       }
+
+       fs_sink->output_dir_path = g_string_new(NULL);
+       fs_sink->self_comp = self_comp;
+       status = configure_component(fs_sink, params);
+       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+               /* configure_component() logs errors */
+               goto end;
+       }
+
+       if (fs_sink->assume_single_trace &&
+                       g_file_test(fs_sink->output_dir_path->str,
+                               G_FILE_TEST_EXISTS)) {
+               BT_LOGE("Single trace mode, but output path exists: "
+                       "output-path=\"%s\"", fs_sink->output_dir_path->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       status = ensure_output_dir_exists(fs_sink);
+       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+               /* ensure_output_dir_exists() logs errors */
+               goto end;
+       }
+
+       fs_sink->traces = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+               NULL, (GDestroyNotify) fs_sink_trace_destroy);
+       if (!fs_sink->traces) {
+               BT_LOGE_STR("Failed to allocate one GHashTable.");
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto end;
+       }
+
+       status = bt_self_component_sink_add_input_port(self_comp, in_port_name,
+               NULL, NULL);
+       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+               goto end;
+       }
+
+       bt_self_component_set_data(
+               bt_self_component_sink_as_self_component(self_comp), fs_sink);
+
+end:
+       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+               destroy_fs_sink_comp(fs_sink);
+       }
+
+       return status;
+}
+
+static inline
+struct fs_sink_stream *borrow_stream(struct fs_sink_comp *fs_sink,
+               const bt_stream *ir_stream)
+{
+       const bt_trace *ir_trace = bt_stream_borrow_trace_const(ir_stream);
+       struct fs_sink_trace *trace;
+       struct fs_sink_stream *stream = NULL;
+
+       trace = g_hash_table_lookup(fs_sink->traces, ir_trace);
+       if (unlikely(!trace)) {
+               if (fs_sink->assume_single_trace &&
+                               g_hash_table_size(fs_sink->traces) > 0) {
+                       BT_LOGE("Single trace mode, but getting more than one trace: "
+                               "stream-name=\"%s\"",
+                               bt_stream_get_name(ir_stream));
+                       goto end;
+               }
+
+               trace = fs_sink_trace_create(fs_sink, ir_trace);
+               if (!trace) {
+                       goto end;
+               }
+       }
+
+       stream = g_hash_table_lookup(trace->streams, ir_stream);
+       if (unlikely(!stream)) {
+               stream = fs_sink_stream_create(trace, ir_stream);
+               if (!stream) {
+                       goto end;
+               }
+       }
+
+end:
+       return stream;
+}
+
+static inline
+bt_self_component_status handle_event_msg(struct fs_sink_comp *fs_sink,
+               const bt_message *msg)
+{
+       int ret;
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_event *ir_event = bt_message_event_borrow_event_const(msg);
+       const bt_stream *ir_stream = bt_event_borrow_stream_const(ir_event);
+       struct fs_sink_stream *stream;
+       struct fs_sink_ctf_event_class *ec = NULL;
+       const bt_clock_snapshot *cs = NULL;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (unlikely(!stream)) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       ret = try_translate_event_class_trace_ir_to_ctf_ir(stream->sc,
+               bt_event_borrow_class_const(ir_event), &ec);
+       if (ret) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       BT_ASSERT(ec);
+
+       if (stream->sc->default_clock_class) {
+               (void) bt_message_event_borrow_default_clock_snapshot_const(
+                       msg, &cs);
+       }
+
+       ret = fs_sink_stream_write_event(stream, cs, ir_event, ec);
+       if (unlikely(ret)) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_packet_beginning_msg(
+               struct fs_sink_comp *fs_sink, const bt_message *msg)
+{
+       int ret;
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_packet *ir_packet =
+               bt_message_packet_beginning_borrow_packet_const(msg);
+       const bt_stream *ir_stream = bt_packet_borrow_stream_const(ir_packet);
+       struct fs_sink_stream *stream;
+       const bt_clock_snapshot *cs = NULL;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (unlikely(!stream)) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (stream->sc->default_clock_class) {
+               (void) bt_message_packet_beginning_borrow_default_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+       }
+
+       if (stream->discarded_events_state.in_range) {
+               /*
+                * Make sure that the current discarded events range's
+                * beginning time matches what's expected for CTF 1.8.
+                */
+               if (stream->sc->default_clock_class) {
+                       uint64_t expected_cs;
+
+                       if (stream->prev_packet_state.end_cs == UINT64_C(-1)) {
+                               /* We're opening the first packet */
+                               expected_cs = bt_clock_snapshot_get_value(cs);
+                       } else {
+                               expected_cs = stream->prev_packet_state.end_cs;
+                       }
+
+                       if (stream->discarded_events_state.beginning_cs !=
+                                       expected_cs) {
+                               BT_LOGE("Incompatible discarded events message: "
+                                       "unexpected beginning time: "
+                                       "beginning-cs-val=%" PRIu64 ", "
+                                       "expected-beginning-cs-val=%" PRIu64 ", "
+                                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                                       "trace-name=\"%s\", path=\"%s/%s\"",
+                                       stream->discarded_events_state.beginning_cs,
+                                       expected_cs,
+                                       bt_stream_get_id(ir_stream),
+                                       bt_stream_get_name(ir_stream),
+                                       bt_trace_get_name(
+                                               bt_stream_borrow_trace_const(ir_stream)),
+                                       stream->trace->path->str, stream->file_name->str);
+                               status = BT_SELF_COMPONENT_STATUS_ERROR;
+                               goto end;
+                       }
+               }
+       }
+
+
+       if (stream->discarded_packets_state.in_range) {
+               if (stream->prev_packet_state.end_cs == UINT64_C(-1)) {
+                       BT_LOGE("Incompatible discarded packets message "
+                               "occuring before the stream's first packet: "
+                               "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                               "trace-name=\"%s\", path=\"%s/%s\"",
+                               bt_stream_get_id(ir_stream),
+                               bt_stream_get_name(ir_stream),
+                               bt_trace_get_name(
+                                       bt_stream_borrow_trace_const(ir_stream)),
+                               stream->trace->path->str, stream->file_name->str);
+                       status = BT_SELF_COMPONENT_STATUS_ERROR;
+                       goto end;
+               }
+
+               /*
+                * Make sure that the current discarded packets range's
+                * beginning and end times match what's expected for CTF
+                * 1.8.
+                */
+               if (stream->sc->default_clock_class) {
+                       uint64_t expected_end_cs =
+                               bt_clock_snapshot_get_value(cs);
+
+                       if (stream->discarded_packets_state.beginning_cs !=
+                                       stream->prev_packet_state.end_cs) {
+                               BT_LOGE("Incompatible discarded packets message: "
+                                       "unexpected beginning time: "
+                                       "beginning-cs-val=%" PRIu64 ", "
+                                       "expected-beginning-cs-val=%" PRIu64 ", "
+                                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                                       "trace-name=\"%s\", path=\"%s/%s\"",
+                                       stream->discarded_packets_state.beginning_cs,
+                                       stream->prev_packet_state.end_cs,
+                                       bt_stream_get_id(ir_stream),
+                                       bt_stream_get_name(ir_stream),
+                                       bt_trace_get_name(
+                                               bt_stream_borrow_trace_const(ir_stream)),
+                                       stream->trace->path->str, stream->file_name->str);
+                               status = BT_SELF_COMPONENT_STATUS_ERROR;
+                               goto end;
+                       }
+
+                       if (stream->discarded_packets_state.end_cs !=
+                                       expected_end_cs) {
+                               BT_LOGE("Incompatible discarded packets message: "
+                                       "unexpected end time: "
+                                       "end-cs-val=%" PRIu64 ", "
+                                       "expected-end-cs-val=%" PRIu64 ", "
+                                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                                       "trace-name=\"%s\", path=\"%s/%s\"",
+                                       stream->discarded_packets_state.beginning_cs,
+                                       expected_end_cs,
+                                       bt_stream_get_id(ir_stream),
+                                       bt_stream_get_name(ir_stream),
+                                       bt_trace_get_name(
+                                               bt_stream_borrow_trace_const(ir_stream)),
+                                       stream->trace->path->str, stream->file_name->str);
+                               status = BT_SELF_COMPONENT_STATUS_ERROR;
+                               goto end;
+                       }
+               }
+       }
+
+       stream->discarded_packets_state.in_range = false;
+       ret = fs_sink_stream_open_packet(stream, cs, ir_packet);
+       if (ret) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_packet_end_msg(
+               struct fs_sink_comp *fs_sink, const bt_message *msg)
+{
+       int ret;
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_packet *ir_packet =
+               bt_message_packet_end_borrow_packet_const(msg);
+       const bt_stream *ir_stream = bt_packet_borrow_stream_const(ir_packet);
+       struct fs_sink_stream *stream;
+       const bt_clock_snapshot *cs = NULL;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (unlikely(!stream)) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (stream->sc->default_clock_class) {
+               (void) bt_message_packet_end_borrow_default_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+       }
+
+       if (stream->sc->default_clock_class) {
+               (void) bt_message_packet_end_borrow_default_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+       }
+
+       if (stream->discarded_events_state.in_range) {
+               /*
+                * Make sure that the current discarded events range's
+                * end time matches what's expected for CTF 1.8.
+                */
+               if (stream->sc->default_clock_class) {
+                       uint64_t expected_cs = bt_clock_snapshot_get_value(cs);
+
+                       if (stream->discarded_events_state.end_cs !=
+                                       expected_cs) {
+                               BT_LOGE("Incompatible discarded events message: "
+                                       "unexpected end time: "
+                                       "end-cs-val=%" PRIu64 ", "
+                                       "expected-end-cs-val=%" PRIu64 ", "
+                                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                                       "trace-name=\"%s\", path=\"%s/%s\"",
+                                       stream->discarded_events_state.end_cs,
+                                       expected_cs,
+                                       bt_stream_get_id(ir_stream),
+                                       bt_stream_get_name(ir_stream),
+                                       bt_trace_get_name(
+                                               bt_stream_borrow_trace_const(ir_stream)),
+                                       stream->trace->path->str, stream->file_name->str);
+                               status = BT_SELF_COMPONENT_STATUS_ERROR;
+                               goto end;
+                       }
+               }
+       }
+
+       ret = fs_sink_stream_close_packet(stream, cs);
+       if (ret) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       stream->discarded_events_state.in_range = false;
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_stream_beginning_msg(
+               struct fs_sink_comp *fs_sink, const bt_message *msg)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_stream *ir_stream =
+               bt_message_stream_beginning_borrow_stream_const(msg);
+       struct fs_sink_stream *stream;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (!stream) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       BT_LOGI("Created new, empty stream file: "
+               "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+               "trace-name=\"%s\", path=\"%s/%s\"",
+               bt_stream_get_id(ir_stream), bt_stream_get_name(ir_stream),
+               bt_trace_get_name(bt_stream_borrow_trace_const(ir_stream)),
+               stream->trace->path->str, stream->file_name->str);
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_stream_end_msg(struct fs_sink_comp *fs_sink,
+               const bt_message *msg)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_stream *ir_stream =
+               bt_message_stream_end_borrow_stream_const(msg);
+       struct fs_sink_stream *stream;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (!stream) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       BT_LOGI("Closing stream file: "
+               "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+               "trace-name=\"%s\", path=\"%s/%s\"",
+               bt_stream_get_id(ir_stream), bt_stream_get_name(ir_stream),
+               bt_trace_get_name(bt_stream_borrow_trace_const(ir_stream)),
+               stream->trace->path->str, stream->file_name->str);
+
+       /*
+        * This destroys the stream object and frees all its resources,
+        * closing the stream file.
+        */
+       g_hash_table_remove(stream->trace->streams, ir_stream);
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_discarded_events_msg(
+               struct fs_sink_comp *fs_sink, const bt_message *msg)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_stream *ir_stream =
+               bt_message_discarded_events_borrow_stream_const(msg);
+       struct fs_sink_stream *stream;
+       const bt_clock_snapshot *cs = NULL;
+       bt_property_availability avail;
+       uint64_t count;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (!stream) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (fs_sink->ignore_discarded_events) {
+               BT_LOGI("Ignoring discarded events message: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               goto end;
+       }
+
+       if (stream->discarded_events_state.in_range) {
+               BT_LOGE("Unsupported contiguous discarded events message: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (stream->packet_state.is_open) {
+               BT_LOGE("Unsupported discarded events message occuring "
+                       "within a packet: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       stream->discarded_events_state.in_range = true;
+
+       if (stream->sc->default_clock_class) {
+               /*
+                * The clock snapshot values will be validated when
+                * handling the next "packet beginning" message.
+                */
+               (void) bt_message_discarded_events_borrow_default_beginning_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+               stream->discarded_events_state.beginning_cs =
+                       bt_clock_snapshot_get_value(cs);
+               cs = NULL;
+               (void) bt_message_discarded_events_borrow_default_end_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+               stream->discarded_events_state.end_cs =
+                       bt_clock_snapshot_get_value(cs);
+       } else {
+               stream->discarded_events_state.beginning_cs = UINT64_C(-1);
+               stream->discarded_events_state.end_cs = UINT64_C(-1);
+       }
+
+       avail = bt_message_discarded_events_get_count(msg, &count);
+       if (avail != BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               count = 1;
+       }
+
+       stream->packet_state.discarded_events_counter += count;
+
+end:
+       return status;
+}
+
+static inline
+bt_self_component_status handle_discarded_packets_msg(
+               struct fs_sink_comp *fs_sink, const bt_message *msg)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       const bt_stream *ir_stream =
+               bt_message_discarded_packets_borrow_stream_const(msg);
+       struct fs_sink_stream *stream;
+       const bt_clock_snapshot *cs = NULL;
+       bt_property_availability avail;
+       uint64_t count;
+
+       stream = borrow_stream(fs_sink, ir_stream);
+       if (!stream) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (fs_sink->ignore_discarded_packets) {
+               BT_LOGI("Ignoring discarded packets message: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               goto end;
+       }
+
+       if (stream->discarded_packets_state.in_range) {
+               BT_LOGE("Unsupported contiguous discarded packets message: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       if (stream->packet_state.is_open) {
+               BT_LOGE("Unsupported discarded packets message occuring "
+                       "within a packet: "
+                       "stream-id=%" PRIu64 ", stream-name=\"%s\", "
+                       "trace-name=\"%s\", path=\"%s/%s\"",
+                       bt_stream_get_id(ir_stream),
+                       bt_stream_get_name(ir_stream),
+                       bt_trace_get_name(
+                               bt_stream_borrow_trace_const(ir_stream)),
+                       stream->trace->path->str, stream->file_name->str);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       stream->discarded_packets_state.in_range = true;
+
+       if (stream->sc->default_clock_class) {
+               /*
+                * The clock snapshot values will be validated when
+                * handling the next "packet beginning" message.
+                */
+               (void) bt_message_discarded_packets_borrow_default_beginning_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+               stream->discarded_packets_state.beginning_cs =
+                       bt_clock_snapshot_get_value(cs);
+               cs = NULL;
+               (void) bt_message_discarded_packets_borrow_default_end_clock_snapshot_const(
+                       msg, &cs);
+               BT_ASSERT(cs);
+               stream->discarded_packets_state.end_cs =
+                       bt_clock_snapshot_get_value(cs);
+       } else {
+               stream->discarded_packets_state.beginning_cs = UINT64_C(-1);
+               stream->discarded_packets_state.end_cs = UINT64_C(-1);
+       }
+
+       avail = bt_message_discarded_packets_get_count(msg, &count);
+       if (avail != BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               count = 1;
+       }
+
+       stream->packet_state.seq_num += count;
+
+end:
+       return status;
+}
+
+static inline
+void put_messages(bt_message_array_const msgs, uint64_t count)
+{
+       uint64_t i;
+
+       for (i = 0; i < count; i++) {
+               BT_MESSAGE_PUT_REF_AND_RESET(msgs[i]);
+       }
+}
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_consume(bt_self_component_sink *self_comp)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       struct fs_sink_comp *fs_sink;
+       bt_message_iterator_status it_status;
+       uint64_t msg_count = 0;
+       bt_message_array_const msgs;
+
+       fs_sink = bt_self_component_get_data(
+                       bt_self_component_sink_as_self_component(self_comp));
+       BT_ASSERT(fs_sink);
+       BT_ASSERT(fs_sink->upstream_iter);
+
+       /* Consume messages */
+       it_status = bt_self_component_port_input_message_iterator_next(
+               fs_sink->upstream_iter, &msgs, &msg_count);
+       if (it_status < 0) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       switch (it_status) {
+       case BT_MESSAGE_ITERATOR_STATUS_OK:
+       {
+               uint64_t i;
+
+               for (i = 0; i < msg_count; i++) {
+                       const bt_message *msg = msgs[i];
+
+                       BT_ASSERT(msg);
+
+                       switch (bt_message_get_type(msg)) {
+                       case BT_MESSAGE_TYPE_EVENT:
+                               status = handle_event_msg(fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_PACKET_BEGINNING:
+                               status = handle_packet_beginning_msg(
+                                       fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_PACKET_END:
+                               status = handle_packet_end_msg(
+                                       fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_MESSAGE_ITERATOR_INACTIVITY:
+                               /* Ignore */
+                               BT_LOGD_STR("Ignoring message iterator inactivity message.");
+                               break;
+                       case BT_MESSAGE_TYPE_STREAM_BEGINNING:
+                               status = handle_stream_beginning_msg(
+                                       fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_STREAM_END:
+                               status = handle_stream_end_msg(
+                                       fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_STREAM_ACTIVITY_BEGINNING:
+                       case BT_MESSAGE_TYPE_STREAM_ACTIVITY_END:
+                               /* Not supported by CTF 1.8 */
+                               BT_LOGD_STR("Ignoring stream activity message.");
+                               break;
+                       case BT_MESSAGE_TYPE_DISCARDED_EVENTS:
+                               status = handle_discarded_events_msg(
+                                       fs_sink, msg);
+                               break;
+                       case BT_MESSAGE_TYPE_DISCARDED_PACKETS:
+                               status = handle_discarded_packets_msg(
+                                       fs_sink, msg);
+                               break;
+                       default:
+                               abort();
+                       }
+
+                       BT_MESSAGE_PUT_REF_AND_RESET(msgs[i]);
+
+                       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+                               BT_LOGE("Failed to handle message: "
+                                       "generated CTF traces could be incomplete: "
+                                       "output-dir-path=\"%s\"",
+                                       fs_sink->output_dir_path->str);
+                               goto error;
+                       }
+               }
+
+               break;
+       }
+       case BT_MESSAGE_ITERATOR_STATUS_AGAIN:
+               status = BT_SELF_COMPONENT_STATUS_AGAIN;
+               break;
+       case BT_MESSAGE_ITERATOR_STATUS_END:
+               /* TODO: Finalize all traces (should already be done?) */
+               status = BT_SELF_COMPONENT_STATUS_END;
+               break;
+       case BT_MESSAGE_ITERATOR_STATUS_NOMEM:
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               break;
+       case BT_MESSAGE_ITERATOR_STATUS_ERROR:
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               break;
+       default:
+               break;
+       }
+
+       goto end;
+
+error:
+       BT_ASSERT(status != BT_SELF_COMPONENT_STATUS_OK);
+       put_messages(msgs, msg_count);
+
+end:
+       return status;
+}
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_graph_is_configured(
+               bt_self_component_sink *self_comp)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       struct fs_sink_comp *fs_sink = bt_self_component_get_data(
+                       bt_self_component_sink_as_self_component(self_comp));
+
+       fs_sink->upstream_iter =
+               bt_self_component_port_input_message_iterator_create(
+                       bt_self_component_sink_borrow_input_port_by_name(
+                               self_comp, in_port_name));
+       if (!fs_sink->upstream_iter) {
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto end;
+       }
+
+end:
+       return status;
+}
+
+BT_HIDDEN
+void ctf_fs_sink_finalize(bt_self_component_sink *self_comp)
+{
+       struct fs_sink_comp *fs_sink = bt_self_component_get_data(
+                       bt_self_component_sink_as_self_component(self_comp));
+
+       destroy_fs_sink_comp(fs_sink);
+}
diff --git a/plugins/ctf/fs-sink/fs-sink.h b/plugins/ctf/fs-sink/fs-sink.h
new file mode 100644 (file)
index 0000000..a41052c
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/babeltrace.h>
+#include <stdbool.h>
+#include <glib.h>
+
+struct fs_sink_comp {
+       bt_self_component_sink *self_comp;
+
+       /* Owned by this */
+       bt_self_component_port_input_message_iterator *upstream_iter;
+
+       /* Base output directory path */
+       GString *output_dir_path;
+
+       bool assume_single_trace;
+       bool ignore_discarded_events;
+       bool ignore_discarded_packets;
+       bool quiet;
+
+       /*
+        * Hash table of `const bt_trace *` (weak) to
+        * `struct fs_sink_trace *` (owned by hash table).
+        */
+       GHashTable *traces;
+};
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_init(
+               bt_self_component_sink *component,
+               const bt_value *params,
+               void *init_method_data);
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_consume(
+               bt_self_component_sink *component);
+
+BT_HIDDEN
+bt_self_component_status ctf_fs_sink_graph_is_configured(
+               bt_self_component_sink *component);
+
+BT_HIDDEN
+void ctf_fs_sink_finalize(bt_self_component_sink *component);
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_FS_SINK_H */
diff --git a/plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.c b/plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.c
new file mode 100644 (file)
index 0000000..fef1d04
--- /dev/null
@@ -0,0 +1,889 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRANSLATE-CTF-IR-TO-TSDL"
+#include "logging.h"
+
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/babeltrace-internal.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <glib.h>
+#include <babeltrace/assert-internal.h>
+#include <babeltrace/endian-internal.h>
+
+#include "fs-sink-ctf-meta.h"
+
+struct ctx {
+       unsigned int indent_level;
+       GString *tsdl;
+};
+
+static inline
+void append_indent(struct ctx *ctx)
+{
+       unsigned int i;
+
+       for (i = 0; i < ctx->indent_level; i++) {
+               g_string_append_c(ctx->tsdl, '\t');
+       }
+}
+
+static
+void append_uuid(struct ctx *ctx, bt_uuid uuid)
+{
+       g_string_append_printf(ctx->tsdl,
+               "\"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\"",
+               (unsigned int) uuid[0],
+               (unsigned int) uuid[1],
+               (unsigned int) uuid[2],
+               (unsigned int) uuid[3],
+               (unsigned int) uuid[4],
+               (unsigned int) uuid[5],
+               (unsigned int) uuid[6],
+               (unsigned int) uuid[7],
+               (unsigned int) uuid[8],
+               (unsigned int) uuid[9],
+               (unsigned int) uuid[10],
+               (unsigned int) uuid[11],
+               (unsigned int) uuid[12],
+               (unsigned int) uuid[13],
+               (unsigned int) uuid[14],
+               (unsigned int) uuid[15]);
+}
+
+static
+void append_quoted_string_content(struct ctx *ctx, const char *str)
+{
+       const char *ch;
+
+       for (ch = str; *ch != '\0'; ch++) {
+               unsigned char uch = (unsigned char) *ch;
+
+               if (uch < 32 || uch >= 127) {
+                       switch (*ch) {
+                       case '\a':
+                               g_string_append(ctx->tsdl, "\\a");
+                               break;
+                       case '\b':
+                               g_string_append(ctx->tsdl, "\\b");
+                               break;
+                       case '\f':
+                               g_string_append(ctx->tsdl, "\\f");
+                               break;
+                       case '\n':
+                               g_string_append(ctx->tsdl, "\\n");
+                               break;
+                       case '\r':
+                               g_string_append(ctx->tsdl, "\\r");
+                               break;
+                       case '\t':
+                               g_string_append(ctx->tsdl, "\\t");
+                               break;
+                       case '\v':
+                               g_string_append(ctx->tsdl, "\\v");
+                               break;
+                       default:
+                               g_string_append_printf(ctx->tsdl, "\\x%02x",
+                                       (unsigned int) uch);
+                               break;
+                       }
+               } else if (*ch == '"' || *ch == '\\') {
+                       g_string_append_c(ctx->tsdl, '\\');
+                       g_string_append_c(ctx->tsdl, *ch);
+               } else {
+                       g_string_append_c(ctx->tsdl, *ch);
+               }
+       }
+}
+
+static
+void append_quoted_string(struct ctx *ctx, const char *str)
+{
+       g_string_append_c(ctx->tsdl, '"');
+       append_quoted_string_content(ctx, str);
+       g_string_append_c(ctx->tsdl, '"');
+}
+
+static
+void append_integer_field_class_from_props(struct ctx *ctx, unsigned int size,
+               unsigned int alignment, bool is_signed,
+               bt_field_class_integer_preferred_display_base disp_base,
+               const char *mapped_clock_class_name, const char *field_name,
+               bool end)
+{
+       g_string_append_printf(ctx->tsdl,
+               "integer { size = %u; align = %u;",
+               size, alignment);
+
+       if (is_signed) {
+               g_string_append(ctx->tsdl, " signed = true;");
+       }
+
+       if (disp_base != BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL) {
+               g_string_append(ctx->tsdl, " base = ");
+
+               switch (disp_base) {
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY:
+                       g_string_append(ctx->tsdl, "b");
+                       break;
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL:
+                       g_string_append(ctx->tsdl, "o");
+                       break;
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL:
+                       g_string_append(ctx->tsdl, "x");
+                       break;
+               default:
+                       abort();
+               }
+
+               g_string_append_c(ctx->tsdl, ';');
+       }
+
+       if (mapped_clock_class_name) {
+               g_string_append_printf(ctx->tsdl, " map = clock.%s.value;",
+                       mapped_clock_class_name);
+       }
+
+       g_string_append(ctx->tsdl, " }");
+
+       if (field_name) {
+               g_string_append_printf(ctx->tsdl, " %s", field_name);
+       }
+
+       if (end) {
+               g_string_append(ctx->tsdl, ";\n");
+       }
+}
+
+static
+void append_end_block(struct ctx *ctx)
+{
+       ctx->indent_level--;
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "}");
+}
+
+static
+void append_end_block_semi_nl(struct ctx *ctx)
+{
+       ctx->indent_level--;
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "};\n");
+}
+
+static
+void append_end_block_semi_nl_nl(struct ctx *ctx)
+{
+       append_end_block_semi_nl(ctx);
+       g_string_append_c(ctx->tsdl, '\n');
+}
+
+static
+void append_integer_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_int *fc)
+{
+       const bt_field_class *ir_fc = fc->base.base.ir_fc;
+       bt_field_class_type type = bt_field_class_get_type(ir_fc);
+       bool is_signed = type == BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION ||
+               type == BT_FIELD_CLASS_TYPE_SIGNED_INTEGER;
+
+       if (type == BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION ||
+                       type == BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION) {
+               g_string_append(ctx->tsdl, "enum : ");
+       }
+
+       append_integer_field_class_from_props(ctx, fc->base.size,
+               fc->base.base.alignment, is_signed,
+               bt_field_class_integer_get_preferred_display_base(ir_fc),
+               NULL, NULL, false);
+
+       if (type == BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION ||
+                       type == BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION) {
+               uint64_t i;
+
+               g_string_append(ctx->tsdl, " {\n");
+               ctx->indent_level++;
+
+               for (i = 0; i < bt_field_class_enumeration_get_mapping_count(ir_fc); i++) {
+                       const char *label;
+                       const bt_field_class_unsigned_enumeration_mapping_ranges *u_ranges;
+                       const bt_field_class_signed_enumeration_mapping_ranges *i_ranges;
+                       uint64_t range_count;
+                       uint64_t range_i;
+
+                       if (is_signed) {
+                               bt_field_class_signed_enumeration_borrow_mapping_by_index_const(
+                                       ir_fc, i, &label, &i_ranges);
+                               range_count = bt_field_class_signed_enumeration_mapping_ranges_get_range_count(
+                                       i_ranges);
+                       } else {
+                               bt_field_class_unsigned_enumeration_borrow_mapping_by_index_const(
+                                       ir_fc, i, &label, &u_ranges);
+                               range_count = bt_field_class_unsigned_enumeration_mapping_ranges_get_range_count(
+                                       u_ranges);
+                       }
+
+                       for (range_i = 0; range_i < range_count; range_i++) {
+                               append_indent(ctx);
+
+                               /*
+                                * Systematically prepend `_` to the
+                                * mapping's label as this could be used
+                                * as the tag of a subsequent variant
+                                * field class and variant FC option
+                                * names are systematically protected
+                                * with a leading `_`.
+                                *
+                                * FIXME: This is temporary as the
+                                * library's API should change to
+                                * decouple variant FC option names from
+                                * selector FC labels. The current
+                                * drawback is that an original label
+                                * `HELLO` becomes `_HELLO` in the
+                                * generated metadata, therefore tools
+                                * expecting `HELLO` could fail.
+                                */
+                               g_string_append(ctx->tsdl, "\"_");
+                               append_quoted_string_content(ctx, label);
+                               g_string_append(ctx->tsdl, "\" = ");
+
+                               if (is_signed) {
+                                       int64_t lower, upper;
+
+                                       bt_field_class_signed_enumeration_mapping_ranges_get_range_by_index(
+                                               i_ranges, range_i,
+                                               &lower, &upper);
+
+                                       if (lower == upper) {
+                                               g_string_append_printf(
+                                                       ctx->tsdl, "%" PRId64,
+                                                       lower);
+                                       } else {
+                                               g_string_append_printf(
+                                                       ctx->tsdl, "%" PRId64 " ... %" PRId64,
+                                                       lower, upper);
+                                       }
+                               } else {
+                                       uint64_t lower, upper;
+
+                                       bt_field_class_unsigned_enumeration_mapping_ranges_get_range_by_index(
+                                               u_ranges, range_i,
+                                               &lower, &upper);
+
+                                       if (lower == upper) {
+                                               g_string_append_printf(
+                                                       ctx->tsdl, "%" PRIu64,
+                                                       lower);
+                                       } else {
+                                               g_string_append_printf(
+                                                       ctx->tsdl, "%" PRIu64 " ... %" PRIu64,
+                                                       lower, upper);
+                                       }
+                               }
+
+                               g_string_append(ctx->tsdl, ",\n");
+                       }
+               }
+
+               append_end_block(ctx);
+       }
+}
+
+static
+void append_float_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_float *fc)
+{
+       unsigned int mant_dig, exp_dig;
+
+       if (bt_field_class_real_is_single_precision(fc->base.base.ir_fc)) {
+               mant_dig = 24;
+               exp_dig = 8;
+       } else {
+               mant_dig = 53;
+               exp_dig = 11;
+       }
+
+       g_string_append_printf(ctx->tsdl,
+               "floating_point { mant_dig = %u; exp_dig = %u; align = %u; }",
+               mant_dig, exp_dig, fc->base.base.alignment);
+}
+
+static
+void append_string_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_float *fc)
+{
+       g_string_append(ctx->tsdl, "string { encoding = UTF8; }");
+}
+
+static
+void append_field_class(struct ctx *ctx, struct fs_sink_ctf_field_class *fc);
+
+static
+void append_member(struct ctx *ctx, const char *name,
+               struct fs_sink_ctf_field_class *fc)
+{
+       GString *lengths = NULL;
+       const char *lengths_str = "";
+
+       while (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY ||
+                       fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE) {
+               if (!lengths) {
+                       lengths = g_string_new(NULL);
+                       BT_ASSERT(lengths);
+               }
+
+               if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY) {
+                       struct fs_sink_ctf_field_class_array *array_fc =
+                               (void *) fc;
+
+                       g_string_append_printf(lengths, "[%" PRIu64 "]",
+                               array_fc->length);
+                       fc = array_fc->base.elem_fc;
+               } else {
+                       struct fs_sink_ctf_field_class_sequence *seq_fc =
+                               (void *) fc;
+
+                       g_string_append_printf(lengths, "[%s]",
+                               seq_fc->length_ref->str);
+                       fc = seq_fc->base.elem_fc;
+               }
+       }
+
+       append_field_class(ctx, fc);
+
+       if (lengths) {
+               lengths_str = lengths->str;
+       }
+
+       g_string_append_printf(ctx->tsdl, " %s%s;\n", name, lengths_str);
+
+       if (lengths) {
+               g_string_free(lengths, TRUE);
+       }
+}
+
+static
+void append_struct_field_class_members(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_struct *struct_fc)
+{
+       uint64_t i;
+
+       for (i = 0; i < struct_fc->members->len; i++) {
+               struct fs_sink_ctf_named_field_class *named_fc =
+                       fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                               struct_fc, i);
+               struct fs_sink_ctf_field_class *fc = named_fc->fc;
+
+               if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE) {
+                       struct fs_sink_ctf_field_class_sequence *seq_fc =
+                               (void *) fc;
+
+                       if (seq_fc->length_is_before) {
+                               append_indent(ctx);
+                               append_integer_field_class_from_props(ctx,
+                                       32, 8, false,
+                                       BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+                                       NULL, seq_fc->length_ref->str, true);
+                       }
+               } else if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT) {
+                       struct fs_sink_ctf_field_class_variant *var_fc =
+                               (void *) fc;
+
+                       if (var_fc->tag_is_before) {
+                               append_indent(ctx);
+                               g_string_append(ctx->tsdl, "enum : ");
+                               append_integer_field_class_from_props(ctx,
+                                       16, 8, false,
+                                       BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+                                       NULL, NULL, false);
+                               g_string_append(ctx->tsdl, " {\n");
+                               ctx->indent_level++;
+
+                               for (i = 0; i < var_fc->options->len; i++) {
+                                       struct fs_sink_ctf_named_field_class *named_fc =
+                                               fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                                                       var_fc, i);
+
+                                       append_indent(ctx);
+                                       g_string_append_printf(ctx->tsdl,
+                                               "\"%s\" = %" PRIu64 ",\n",
+                                               named_fc->name->str, i);
+                               }
+
+                               append_end_block(ctx);
+                               g_string_append_printf(ctx->tsdl, " %s;\n",
+                                       var_fc->tag_ref->str);
+                       }
+               }
+
+               append_indent(ctx);
+               append_member(ctx, named_fc->name->str, fc);
+       }
+}
+
+static
+void append_struct_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_struct *fc)
+{
+       g_string_append(ctx->tsdl, "struct {\n");
+       ctx->indent_level++;
+       append_struct_field_class_members(ctx, fc);
+       append_end_block(ctx);
+       g_string_append_printf(ctx->tsdl, " align(%u)",
+               fc->base.alignment);
+}
+
+static
+void append_variant_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_variant *var_fc)
+{
+       uint64_t i;
+
+       g_string_append_printf(ctx->tsdl, "variant <%s> {\n",
+               var_fc->tag_ref->str);
+       ctx->indent_level++;
+
+       for (i = 0; i < var_fc->options->len; i++) {
+               struct fs_sink_ctf_named_field_class *named_fc =
+                       fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                               var_fc, i);
+
+               append_indent(ctx);
+               append_member(ctx, named_fc->name->str, named_fc->fc);
+       }
+
+       append_end_block(ctx);
+}
+
+static
+void append_field_class(struct ctx *ctx, struct fs_sink_ctf_field_class *fc)
+{
+       switch (fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_INT:
+               append_integer_field_class(ctx, (void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_FLOAT:
+               append_float_field_class(ctx, (void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRING:
+               append_string_field_class(ctx, (void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               append_struct_field_class(ctx, (void *) fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+               append_variant_field_class(ctx, (void *) fc);
+               break;
+       default:
+               abort();
+       }
+}
+
+static
+void append_event_class(struct ctx *ctx, struct fs_sink_ctf_event_class *ec)
+{
+       const char *str;
+       bt_event_class_log_level log_level;
+
+       /* Event class */
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "event {\n");
+       ctx->indent_level++;
+
+       /* Event class properties */
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "name = ");
+       str = bt_event_class_get_name(ec->ir_ec);
+       if (!str) {
+               str = "unknown";
+       }
+
+       append_quoted_string(ctx, str);
+       g_string_append(ctx->tsdl, ";\n");
+       append_indent(ctx);
+       g_string_append_printf(ctx->tsdl, "stream_id = %" PRIu64 ";\n",
+               bt_stream_class_get_id(ec->sc->ir_sc));
+       append_indent(ctx);
+       g_string_append_printf(ctx->tsdl, "id = %" PRIu64 ";\n",
+               bt_event_class_get_id(ec->ir_ec));
+
+       str = bt_event_class_get_emf_uri(ec->ir_ec);
+       if (str) {
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "model.emf.uri = ");
+               append_quoted_string(ctx, str);
+               g_string_append(ctx->tsdl, ";\n");
+       }
+
+       if (bt_event_class_get_log_level(ec->ir_ec, &log_level) ==
+                       BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               unsigned int level;
+
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "loglevel = ");
+
+               switch (log_level) {
+               case BT_EVENT_CLASS_LOG_LEVEL_EMERGENCY:
+                       level = 0;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_ALERT:
+                       level = 1;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_CRITICAL:
+                       level = 2;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_ERROR:
+                       level = 3;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_WARNING:
+                       level = 4;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_NOTICE:
+                       level = 5;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_INFO:
+                       level = 6;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_SYSTEM:
+                       level = 7;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROGRAM:
+                       level = 8;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROCESS:
+                       level = 9;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_MODULE:
+                       level = 10;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_UNIT:
+                       level = 11;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_FUNCTION:
+                       level = 12;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_LINE:
+                       level = 13;
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG:
+                       level = 14;
+                       break;
+               default:
+                       abort();
+               }
+
+               g_string_append_printf(ctx->tsdl, "%u;\n", level);
+       }
+
+       /* Event specific context field class */
+       if (ec->spec_context_fc) {
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "context := ");
+               append_field_class(ctx, ec->spec_context_fc);
+               g_string_append(ctx->tsdl, ";\n");
+       }
+
+       /* Event payload field class */
+       if (ec->payload_fc) {
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "fields := ");
+               append_field_class(ctx, ec->payload_fc);
+               g_string_append(ctx->tsdl, ";\n");
+       }
+
+       append_end_block_semi_nl_nl(ctx);
+}
+
+static
+void append_stream_class(struct ctx *ctx,
+               struct fs_sink_ctf_stream_class *sc)
+{
+       uint64_t i;
+
+       /* Default clock class */
+       if (sc->default_clock_class) {
+               const char *descr;
+               int64_t offset_seconds;
+               uint64_t offset_cycles;
+               bt_uuid uuid;
+
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "clock {\n");
+               ctx->indent_level++;
+               BT_ASSERT(sc->default_clock_class_name->len > 0);
+               append_indent(ctx);
+               g_string_append_printf(ctx->tsdl, "name = %s;\n",
+                       sc->default_clock_class_name->str);
+               descr = bt_clock_class_get_description(sc->default_clock_class);
+               if (descr) {
+                       append_indent(ctx);
+                       g_string_append(ctx->tsdl, "description = ");
+                       append_quoted_string(ctx, descr);
+                       g_string_append(ctx->tsdl, ";\n");
+               }
+
+               append_indent(ctx);
+               g_string_append_printf(ctx->tsdl, "freq = %" PRIu64 ";\n",
+                       bt_clock_class_get_frequency(sc->default_clock_class));
+               append_indent(ctx);
+               g_string_append_printf(ctx->tsdl, "precision = %" PRIu64 ";\n",
+                       bt_clock_class_get_precision(sc->default_clock_class));
+               bt_clock_class_get_offset(sc->default_clock_class,
+                       &offset_seconds, &offset_cycles);
+               append_indent(ctx);
+               g_string_append_printf(ctx->tsdl, "offset_s = %" PRId64 ";\n",
+                       offset_seconds);
+               append_indent(ctx);
+               g_string_append_printf(ctx->tsdl, "offset = %" PRIu64 ";\n",
+                       offset_cycles);
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "absolute = ");
+
+               if (bt_clock_class_origin_is_unix_epoch(
+                               sc->default_clock_class)) {
+                       g_string_append(ctx->tsdl, "true");
+               } else {
+                       g_string_append(ctx->tsdl, "false");
+               }
+
+               g_string_append(ctx->tsdl, ";\n");
+               uuid = bt_clock_class_get_uuid(sc->default_clock_class);
+               if (uuid) {
+                       append_indent(ctx);
+                       g_string_append(ctx->tsdl, "uuid = ");
+                       append_uuid(ctx, uuid);
+                       g_string_append(ctx->tsdl, ";\n");
+               }
+
+               /* End clock class */
+               append_end_block_semi_nl_nl(ctx);
+       }
+
+       /* Stream class */
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "stream {\n");
+       ctx->indent_level++;
+
+       /* Stream class properties */
+       append_indent(ctx);
+       g_string_append_printf(ctx->tsdl, "id = %" PRIu64 ";\n",
+               bt_stream_class_get_id(sc->ir_sc));
+
+       /* Packet context field class */
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "packet.context := struct {\n");
+       ctx->indent_level++;
+       append_indent(ctx);
+       append_integer_field_class_from_props(ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "packet_size", true);
+       append_indent(ctx);
+       append_integer_field_class_from_props(ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "content_size", true);
+
+       if (sc->default_clock_class) {
+               append_indent(ctx);
+               append_integer_field_class_from_props(ctx, 64, 8, false,
+                       BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+                       sc->default_clock_class_name->str,
+                       "timestamp_begin", true);
+               append_indent(ctx);
+               append_integer_field_class_from_props(ctx, 64, 8, false,
+                       BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+                       sc->default_clock_class_name->str,
+                       "timestamp_end", true);
+       }
+
+       append_indent(ctx);
+       append_integer_field_class_from_props(ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "events_discarded", true);
+       append_indent(ctx);
+       append_integer_field_class_from_props(ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "packet_seq_num", true);
+
+       if (sc->packet_context_fc) {
+               append_struct_field_class_members(ctx,
+                       (void *) sc->packet_context_fc);
+               fs_sink_ctf_field_class_struct_align_at_least(
+                       (void *) sc->packet_context_fc, 8);
+       }
+
+       /* End packet context field class */
+       append_end_block(ctx);
+       g_string_append_printf(ctx->tsdl, " align(%u);\n\n",
+               sc->packet_context_fc ? sc->packet_context_fc->alignment : 8);
+
+       /* Event header field class */
+       append_indent(ctx);
+       g_string_append(ctx->tsdl, "event.header := struct {\n");
+       ctx->indent_level++;
+       append_indent(ctx);
+       append_integer_field_class_from_props(ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "id", true);
+
+       if (sc->default_clock_class) {
+               append_indent(ctx);
+               append_integer_field_class_from_props(ctx, 64, 8, false,
+                       BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+                       sc->default_clock_class_name->str,
+                       "timestamp", true);
+       }
+
+       /* End event header field class */
+       append_end_block(ctx);
+       g_string_append(ctx->tsdl, " align(8);\n");
+
+       /* Event common context field class */
+       if (sc->event_common_context_fc) {
+               append_indent(ctx);
+               g_string_append(ctx->tsdl, "event.context := ");
+               append_field_class(ctx,
+                       (void *) sc->event_common_context_fc);
+               g_string_append(ctx->tsdl, ";\n");
+       }
+
+       /* End stream class */
+       append_end_block_semi_nl_nl(ctx);
+
+       /* Event classes */
+       for (i = 0; i < sc->event_classes->len; i++) {
+               append_event_class(ctx, sc->event_classes->pdata[i]);
+       }
+}
+
+BT_HIDDEN
+void translate_trace_class_ctf_ir_to_tsdl(struct fs_sink_ctf_trace_class *tc,
+               GString *tsdl)
+{
+       struct ctx ctx = {
+               .indent_level = 0,
+               .tsdl = tsdl,
+       };
+       uint64_t i;
+       uint64_t count;
+
+       g_string_assign(tsdl, "/* CTF 1.8 */\n\n");
+       g_string_append(tsdl, "/* This was generated by a Babeltrace `sink.ctf.fs` component. */\n\n");
+
+       /* Trace class */
+       append_indent(&ctx);
+       g_string_append(tsdl, "trace {\n");
+       ctx.indent_level++;
+
+       /* Trace class properties */
+       append_indent(&ctx);
+       g_string_append(tsdl, "major = 1;\n");
+       append_indent(&ctx);
+       g_string_append(tsdl, "minor = 8;\n");
+       append_indent(&ctx);
+       g_string_append(tsdl, "uuid = ");
+       append_uuid(&ctx, tc->uuid);
+       g_string_append(tsdl, ";\n");
+       append_indent(&ctx);
+       g_string_append(tsdl, "byte_order = ");
+
+       if (BYTE_ORDER == LITTLE_ENDIAN) {
+               g_string_append(tsdl, "le");
+       } else {
+               g_string_append(tsdl, "be");
+       }
+
+       g_string_append(tsdl, ";\n");
+
+       /* Packet header field class */
+       append_indent(&ctx);
+       g_string_append(tsdl, "packet.header := struct {\n");
+       ctx.indent_level++;
+       append_indent(&ctx);
+       append_integer_field_class_from_props(&ctx, 32, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL,
+               NULL, "magic", true);
+       append_indent(&ctx);
+       append_integer_field_class_from_props(&ctx, 8, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "uuid[16]", true);
+       append_indent(&ctx);
+       append_integer_field_class_from_props(&ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "stream_id", true);
+       append_indent(&ctx);
+       append_integer_field_class_from_props(&ctx, 64, 8, false,
+               BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL,
+               NULL, "stream_instance_id", true);
+
+       /* End packet header field class */
+       append_end_block(&ctx);
+       g_string_append(ctx.tsdl, " align(8);\n");
+
+       /* End trace class */
+       append_end_block_semi_nl_nl(&ctx);
+
+       /* Trace class environment */
+       count = bt_trace_class_get_environment_entry_count(tc->ir_tc);
+       if (count > 0) {
+               append_indent(&ctx);
+               g_string_append(tsdl, "env {\n");
+               ctx.indent_level++;
+
+               for (i = 0; i < count; i++) {
+                       const char *name;
+                       const bt_value *val;
+
+                       bt_trace_class_borrow_environment_entry_by_index_const(
+                               tc->ir_tc, i, &name, &val);
+                       append_indent(&ctx);
+                       g_string_append_printf(tsdl, "%s = ", name);
+
+                       switch (bt_value_get_type(val)) {
+                       case BT_VALUE_TYPE_INTEGER:
+                               g_string_append_printf(tsdl, "%" PRId64,
+                                       bt_value_integer_get(val));
+                               break;
+                       case BT_VALUE_TYPE_STRING:
+                               append_quoted_string(&ctx, bt_value_string_get(val));
+                               break;
+                       default:
+                               /*
+                                * This is checked in
+                                * translate_trace_class_trace_ir_to_ctf_ir().
+                                */
+                               abort();
+                       }
+
+                       g_string_append(tsdl, ";\n");
+               }
+
+               /* End trace class environment */
+               append_end_block_semi_nl_nl(&ctx);
+       }
+
+       /* Stream classes and their event classes */
+       for (i = 0; i < tc->stream_classes->len; i++) {
+               append_stream_class(&ctx, tc->stream_classes->pdata[i]);
+       }
+}
diff --git a/plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.h b/plugins/ctf/fs-sink/translate-ctf-ir-to-tsdl.h
new file mode 100644 (file)
index 0000000..4ed0262
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_CTF_IR_TO_TSDL_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_CTF_IR_TO_TSDL_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <glib.h>
+
+#include "fs-sink-ctf-meta.h"
+
+BT_HIDDEN
+void translate_trace_class_ctf_ir_to_tsdl(struct fs_sink_ctf_trace_class *tc,
+               GString *tsdl);
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_CTF_IR_TO_TSDL_H */
diff --git a/plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.c b/plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.c
new file mode 100644 (file)
index 0000000..9f0026c
--- /dev/null
@@ -0,0 +1,1284 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRANSLATE-TRACE-IR-TO-CTF-IR"
+#include "logging.h"
+
+#include <babeltrace/babeltrace.h>
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/common-internal.h>
+#include <babeltrace/assert-internal.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <glib.h>
+
+#include "fs-sink-ctf-meta.h"
+
+struct field_path_elem {
+       uint64_t index_in_parent;
+       GString *name;
+
+       /* Weak */
+       const bt_field_class *ir_fc;
+
+       /* Weak */
+       struct fs_sink_ctf_field_class *parent_fc;
+};
+
+struct ctx {
+       /* Weak */
+       struct fs_sink_ctf_stream_class *cur_sc;
+
+       /* Weak */
+       struct fs_sink_ctf_event_class *cur_ec;
+
+       bt_scope cur_scope;
+
+       /*
+        * Array of `struct field_path_elem` */
+       GArray *cur_path;
+};
+
+static inline
+struct field_path_elem *cur_path_stack_at(struct ctx *ctx, uint64_t i)
+{
+       BT_ASSERT(i < ctx->cur_path->len);
+       return &g_array_index(ctx->cur_path, struct field_path_elem, i);
+}
+
+static inline
+struct field_path_elem *cur_path_stack_top(struct ctx *ctx)
+{
+       BT_ASSERT(ctx->cur_path->len > 0);
+       return cur_path_stack_at(ctx, ctx->cur_path->len - 1);
+}
+
+static inline
+bool is_reserved_member_name(const char *name, const char *reserved_name)
+{
+       bool is_reserved = false;
+
+       if (strcmp(name, reserved_name) == 0) {
+               is_reserved = true;
+               goto end;
+       }
+
+       if (name[0] == '_' && strcmp(&name[1], reserved_name) == 0) {
+               is_reserved = true;
+               goto end;
+       }
+
+end:
+       return is_reserved;
+}
+
+static inline
+int cur_path_stack_push(struct ctx *ctx,
+               uint64_t index_in_parent, const char *ir_name,
+               const bt_field_class *ir_fc,
+               struct fs_sink_ctf_field_class *parent_fc)
+{
+       int ret = 0;
+       struct field_path_elem *field_path_elem;
+
+       g_array_set_size(ctx->cur_path, ctx->cur_path->len + 1);
+       field_path_elem = cur_path_stack_top(ctx);
+       field_path_elem->index_in_parent = index_in_parent;
+       field_path_elem->name = g_string_new(ir_name);
+
+       if (ir_name) {
+               if (ctx->cur_scope == BT_SCOPE_PACKET_CONTEXT) {
+                       if (is_reserved_member_name(ir_name, "packet_size") ||
+                                       is_reserved_member_name(ir_name, "content_size") ||
+                                       is_reserved_member_name(ir_name, "timestamp_begin") ||
+                                       is_reserved_member_name(ir_name, "timestamp_end") ||
+                                       is_reserved_member_name(ir_name, "events_discarded") ||
+                                       is_reserved_member_name(ir_name, "packet_seq_num")) {
+                               BT_LOGE("Unsupported reserved TSDL structure field class member "
+                                       "or variant field class option name: name=\"%s\"",
+                                       ir_name);
+                               ret = -1;
+                               goto end;
+                       }
+               }
+
+               ret = fs_sink_ctf_protect_name(field_path_elem->name);
+               if (ret) {
+                       BT_LOGE("Unsupported non-TSDL structure field class member "
+                               "or variant field class option name: name=\"%s\"",
+                               ir_name);
+                       goto end;
+               }
+       }
+
+       field_path_elem->ir_fc = ir_fc;
+       field_path_elem->parent_fc = parent_fc;
+
+end:
+       return ret;
+}
+
+static inline
+void cur_path_stack_pop(struct ctx *ctx)
+{
+       struct field_path_elem *field_path_elem;
+
+       BT_ASSERT(ctx->cur_path->len > 0);
+       field_path_elem = cur_path_stack_top(ctx);
+
+       if (field_path_elem->name) {
+               g_string_free(field_path_elem->name, TRUE);
+               field_path_elem->name = NULL;
+       }
+
+       g_array_set_size(ctx->cur_path, ctx->cur_path->len - 1);
+}
+
+static inline
+struct field_path_elem *get_field_path_elem(
+               GArray *full_field_path, uint64_t i)
+{
+       return &g_array_index(full_field_path, struct field_path_elem,
+               i);
+}
+
+
+/*
+ * Creates a relative field ref (a single name) from IR field path
+ * `tgt_ir_field_path`.
+ *
+ * This function tries to locate the target field class recursively from
+ * the top to the bottom of the context's current path using only the
+ * target field class's own name. This is because many CTF reading tools
+ * do not support a relative field ref with more than one element, for
+ * example `prev_struct.len`.
+ *
+ * Returns a negative value if this resolving operation failed.
+ */
+static
+int create_relative_field_ref(struct ctx *ctx,
+               const bt_field_path *tgt_ir_field_path, GString *tgt_field_ref)
+{
+       int ret = 0;
+       struct fs_sink_ctf_field_class *tgt_fc = NULL;
+       uint64_t i;
+       int64_t si;
+       const char *tgt_fc_name;
+       struct field_path_elem *field_path_elem;
+
+       /* Get target field class's name */
+       switch (bt_field_path_get_root_scope(tgt_ir_field_path)) {
+       case BT_SCOPE_PACKET_CONTEXT:
+               BT_ASSERT(ctx->cur_sc);
+               tgt_fc = ctx->cur_sc->packet_context_fc;
+               break;
+       case BT_SCOPE_EVENT_COMMON_CONTEXT:
+               BT_ASSERT(ctx->cur_sc);
+               tgt_fc = ctx->cur_sc->event_common_context_fc;
+               break;
+       case BT_SCOPE_EVENT_SPECIFIC_CONTEXT:
+               BT_ASSERT(ctx->cur_ec);
+               tgt_fc = ctx->cur_ec->spec_context_fc;
+               break;
+       case BT_SCOPE_EVENT_PAYLOAD:
+               BT_ASSERT(ctx->cur_ec);
+               tgt_fc = ctx->cur_ec->payload_fc;
+               break;
+       default:
+               abort();
+       }
+
+       i = 0;
+
+       while (i < bt_field_path_get_index_count(tgt_ir_field_path)) {
+               uint64_t index = bt_field_path_get_index_by_index(
+                       tgt_ir_field_path, i);
+               struct fs_sink_ctf_named_field_class *named_fc = NULL;
+
+               BT_ASSERT(tgt_fc);
+
+               switch (tgt_fc->type) {
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+                       named_fc = fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                               (void *) tgt_fc, index);
+                       break;
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+                       named_fc = fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                               (void *) tgt_fc, index);
+                       break;
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+               {
+                       struct fs_sink_ctf_field_class_array_base *array_base_fc =
+                               (void *) tgt_fc;
+
+                       tgt_fc = array_base_fc->elem_fc;
+                       break;
+               }
+               default:
+                       abort();
+               }
+
+               if (named_fc) {
+                       tgt_fc = named_fc->fc;
+                       tgt_fc_name = named_fc->name->str;
+                       i++;
+               }
+       }
+
+       BT_ASSERT(tgt_fc);
+       BT_ASSERT(tgt_fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_INT);
+       BT_ASSERT(tgt_fc_name);
+
+       /* Find target field class having this name in current context */
+       for (si = ctx->cur_path->len - 1; si >= 0; si--) {
+               struct fs_sink_ctf_field_class *fc;
+               struct fs_sink_ctf_field_class_struct *struct_fc;
+               struct fs_sink_ctf_field_class_variant *var_fc;
+               struct fs_sink_ctf_named_field_class *named_fc;
+               uint64_t len;
+
+               field_path_elem = cur_path_stack_at(ctx, (uint64_t) si);
+               fc = field_path_elem->parent_fc;
+               if (!fc) {
+                       /* Reached stack's bottom */
+                       ret = -1;
+                       goto end;
+               }
+
+               switch (fc->type) {
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+                       break;
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+                       continue;
+               default:
+                       /* Not supported by TSDL 1.8 */
+                       ret = -1;
+                       goto end;
+               }
+
+               if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT) {
+                       struct_fc = (void *) fc;
+                       len = struct_fc->members->len;
+               } else {
+                       var_fc = (void *) fc;
+                       len = var_fc->options->len;
+               }
+
+               for (i = 0; i < len; i++) {
+                       if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT) {
+                               named_fc = fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                                       struct_fc, i);
+                       } else {
+                               named_fc = fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                                       var_fc, i);
+                       }
+
+                       if (strcmp(named_fc->name->str, tgt_fc_name) == 0) {
+                               if (named_fc->fc == tgt_fc) {
+                                       g_string_assign(tgt_field_ref,
+                                               tgt_fc_name);
+                               } else {
+                                       /*
+                                        * Using only the target field
+                                        * class's name, we're not
+                                        * reaching the target field
+                                        * class. This is not supported
+                                        * by TSDL 1.8.
+                                        */
+                                       ret = -1;
+                               }
+
+                               goto end;
+                       }
+               }
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * Creates an absolute field ref from IR field path `tgt_ir_field_path`.
+ *
+ * Returns a negative value if this resolving operation failed.
+ */
+static
+int create_absolute_field_ref(struct ctx *ctx,
+               const bt_field_path *tgt_ir_field_path, GString *tgt_field_ref)
+{
+       int ret = 0;
+       struct fs_sink_ctf_field_class *fc = NULL;
+       uint64_t i;
+
+       switch (bt_field_path_get_root_scope(tgt_ir_field_path)) {
+       case BT_SCOPE_PACKET_CONTEXT:
+               BT_ASSERT(ctx->cur_sc);
+               fc = ctx->cur_sc->packet_context_fc;
+               g_string_assign(tgt_field_ref, "stream.packet.context");
+               break;
+       case BT_SCOPE_EVENT_COMMON_CONTEXT:
+               BT_ASSERT(ctx->cur_sc);
+               fc = ctx->cur_sc->event_common_context_fc;
+               g_string_assign(tgt_field_ref, "stream.event.context");
+               break;
+       case BT_SCOPE_EVENT_SPECIFIC_CONTEXT:
+               BT_ASSERT(ctx->cur_ec);
+               fc = ctx->cur_ec->spec_context_fc;
+               g_string_assign(tgt_field_ref, "event.context");
+               break;
+       case BT_SCOPE_EVENT_PAYLOAD:
+               BT_ASSERT(ctx->cur_ec);
+               fc = ctx->cur_ec->payload_fc;
+               g_string_assign(tgt_field_ref, "event.fields");
+               break;
+       default:
+               abort();
+       }
+
+       BT_ASSERT(fc);
+
+       for (i = 0; i < bt_field_path_get_index_count(tgt_ir_field_path); i++) {
+               uint64_t index = bt_field_path_get_index_by_index(
+                       tgt_ir_field_path, i);
+               struct fs_sink_ctf_named_field_class *named_fc = NULL;
+
+               switch (fc->type) {
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+                       named_fc = fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                               (void *) fc, index);
+                       break;
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+                       named_fc = fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                               (void *) fc, index);
+                       break;
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+               case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+                       /* Not supported by TSDL 1.8 */
+                       ret = -1;
+                       goto end;
+               default:
+                       abort();
+               }
+
+               BT_ASSERT(named_fc);
+               g_string_append_c(tgt_field_ref, '.');
+               g_string_append(tgt_field_ref, named_fc->name->str);
+               fc = named_fc->fc;
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * Resolves a target field class located at `tgt_ir_field_path`, writing
+ * the resolved field ref to `tgt_field_ref` and setting
+ * `*create_before` according to whether or not the target field must be
+ * created immediately before (in which case `tgt_field_ref` is
+ * irrelevant).
+ */
+static
+void resolve_field_class(struct ctx *ctx,
+               const bt_field_path *tgt_ir_field_path,
+               GString *tgt_field_ref, bool *create_before)
+{
+       int ret;
+       bt_scope tgt_scope;
+
+       *create_before = false;
+
+       if (!tgt_ir_field_path) {
+               *create_before = true;
+               goto end;
+       }
+
+       tgt_scope = bt_field_path_get_root_scope(tgt_ir_field_path);
+
+       if (tgt_scope == ctx->cur_scope) {
+               /*
+                * Try, in this order:
+                *
+                * 1. Use a relative path, using only the target field
+                *    class's name. This is what is the most commonly
+                *    supported by popular CTF reading tools.
+                *
+                * 2. Use an absolute path. This could fail if there's
+                *    an array field class from the current root's field
+                *    class to the target field class.
+                *
+                * 3. Create the target field class before the
+                *    requesting field class (fallback).
+                */
+               ret = create_relative_field_ref(ctx, tgt_ir_field_path,
+                       tgt_field_ref);
+               if (ret) {
+                       ret = create_absolute_field_ref(ctx, tgt_ir_field_path,
+                               tgt_field_ref);
+                       if (ret) {
+                               *create_before = true;
+                               ret = 0;
+                               goto end;
+                       }
+               }
+       } else {
+               ret = create_absolute_field_ref(ctx, tgt_ir_field_path,
+                       tgt_field_ref);
+
+               /* It must always work in previous scopes */
+               BT_ASSERT(ret == 0);
+       }
+
+end:
+       return;
+}
+
+static
+int translate_field_class(struct ctx *ctx);
+
+static inline
+void append_to_parent_field_class(struct ctx *ctx,
+               struct fs_sink_ctf_field_class *fc)
+{
+       struct fs_sink_ctf_field_class *parent_fc =
+               cur_path_stack_top(ctx)->parent_fc;
+
+       switch (parent_fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               fs_sink_ctf_field_class_struct_append_member((void *) parent_fc,
+                       cur_path_stack_top(ctx)->name->str, fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+               fs_sink_ctf_field_class_variant_append_option((void *) parent_fc,
+                       cur_path_stack_top(ctx)->name->str, fc);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+       {
+               struct fs_sink_ctf_field_class_array_base *array_base_fc =
+                       (void *) parent_fc;
+
+               BT_ASSERT(!array_base_fc->elem_fc);
+               array_base_fc->elem_fc = fc;
+               array_base_fc->base.alignment = fc->alignment;
+               break;
+       }
+       default:
+               abort();
+       }
+}
+
+static inline
+void update_parent_field_class_alignment(struct ctx *ctx,
+               unsigned int alignment)
+{
+       struct fs_sink_ctf_field_class *parent_fc =
+               cur_path_stack_top(ctx)->parent_fc;
+
+       switch (parent_fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+               fs_sink_ctf_field_class_struct_align_at_least(
+                       (void *) parent_fc, alignment);
+               break;
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+       {
+               struct fs_sink_ctf_field_class_array_base *array_base_fc =
+                       (void *) parent_fc;
+
+               array_base_fc->base.alignment = alignment;
+               break;
+       }
+       default:
+               break;
+       }
+}
+
+static inline
+int translate_structure_field_class_members(struct ctx *ctx,
+               struct fs_sink_ctf_field_class_struct *struct_fc,
+               const bt_field_class *ir_fc)
+{
+       int ret = 0;
+       uint64_t i;
+
+       for (i = 0; i < bt_field_class_structure_get_member_count(ir_fc); i++) {
+               const bt_field_class_structure_member *member;
+               const char *name;
+               const bt_field_class *memb_ir_fc;
+
+               member =
+                       bt_field_class_structure_borrow_member_by_index_const(
+                               ir_fc, i);
+               name = bt_field_class_structure_member_get_name(member);
+               memb_ir_fc = bt_field_class_structure_member_borrow_field_class_const(
+                       member);
+               ret = cur_path_stack_push(ctx, i, name, memb_ir_fc,
+                       (void *) struct_fc);
+               if (ret) {
+                       BT_LOGE("Cannot translate structure field class member: "
+                               "name=\"%s\"", name);
+                       goto end;
+               }
+
+               ret = translate_field_class(ctx);
+               if (ret) {
+                       BT_LOGE("Cannot translate structure field class member: "
+                               "name=\"%s\"", name);
+                       goto end;
+               }
+
+               cur_path_stack_pop(ctx);
+       }
+
+end:
+       return ret;
+}
+
+static inline
+int translate_structure_field_class(struct ctx *ctx)
+{
+       int ret;
+       struct fs_sink_ctf_field_class_struct *fc =
+               fs_sink_ctf_field_class_struct_create_empty(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+
+       BT_ASSERT(fc);
+       append_to_parent_field_class(ctx, (void *) fc);
+       ret = translate_structure_field_class_members(ctx, fc, fc->base.ir_fc);
+       if (ret) {
+               goto end;
+       }
+
+       update_parent_field_class_alignment(ctx, fc->base.alignment);
+
+end:
+       return ret;
+}
+
+static inline
+int translate_variant_field_class(struct ctx *ctx)
+{
+       int ret = 0;
+       uint64_t i;
+       struct fs_sink_ctf_field_class_variant *fc =
+               fs_sink_ctf_field_class_variant_create_empty(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+
+       BT_ASSERT(fc);
+
+       /* Resolve tag field class before appending to parent */
+       resolve_field_class(ctx,
+               bt_field_class_variant_borrow_selector_field_path_const(
+                       fc->base.ir_fc), fc->tag_ref, &fc->tag_is_before);
+
+       append_to_parent_field_class(ctx, (void *) fc);
+
+       for (i = 0; i < bt_field_class_variant_get_option_count(fc->base.ir_fc);
+                       i++) {
+               const bt_field_class_variant_option *opt;
+               const char *name;
+               const bt_field_class *opt_ir_fc;
+
+               opt = bt_field_class_variant_borrow_option_by_index_const(
+                       fc->base.ir_fc, i);
+               name = bt_field_class_variant_option_get_name(opt);
+               opt_ir_fc = bt_field_class_variant_option_borrow_field_class_const(
+                       opt);
+               ret = cur_path_stack_push(ctx, i, name, opt_ir_fc, (void *) fc);
+               if (ret) {
+                       BT_LOGE("Cannot translate variant field class option: "
+                               "name=\"%s\"", name);
+                       goto end;
+               }
+
+               ret = translate_field_class(ctx);
+               if (ret) {
+                       BT_LOGE("Cannot translate variant field class option: "
+                               "name=\"%s\"", name);
+                       goto end;
+               }
+
+               cur_path_stack_pop(ctx);
+       }
+
+end:
+       return ret;
+}
+
+static inline
+int translate_static_array_field_class(struct ctx *ctx)
+{
+       struct fs_sink_ctf_field_class_array *fc =
+               fs_sink_ctf_field_class_array_create_empty(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+       const bt_field_class *elem_ir_fc =
+               bt_field_class_array_borrow_element_field_class_const(
+                       fc->base.base.ir_fc);
+       int ret;
+
+       BT_ASSERT(fc);
+       append_to_parent_field_class(ctx, (void *) fc);
+       ret = cur_path_stack_push(ctx, UINT64_C(-1), NULL, elem_ir_fc,
+               (void *) fc);
+       if (ret) {
+               BT_LOGE_STR("Cannot translate static array field class element.");
+               goto end;
+       }
+
+       ret = translate_field_class(ctx);
+       if (ret) {
+               BT_LOGE_STR("Cannot translate static array field class element.");
+               goto end;
+       }
+
+       cur_path_stack_pop(ctx);
+       update_parent_field_class_alignment(ctx, fc->base.base.alignment);
+
+end:
+       return ret;
+}
+
+static inline
+int translate_dynamic_array_field_class(struct ctx *ctx)
+{
+       struct fs_sink_ctf_field_class_sequence *fc =
+               fs_sink_ctf_field_class_sequence_create_empty(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+       const bt_field_class *elem_ir_fc =
+               bt_field_class_array_borrow_element_field_class_const(
+                       fc->base.base.ir_fc);
+       int ret;
+
+       BT_ASSERT(fc);
+
+       /* Resolve length field class before appending to parent */
+       resolve_field_class(ctx,
+               bt_field_class_dynamic_array_borrow_length_field_path_const(
+                       fc->base.base.ir_fc),
+               fc->length_ref, &fc->length_is_before);
+
+       append_to_parent_field_class(ctx, (void *) fc);
+       ret = cur_path_stack_push(ctx, UINT64_C(-1), NULL, elem_ir_fc,
+               (void *) fc);
+       if (ret) {
+               BT_LOGE_STR("Cannot translate dynamic array field class element.");
+               goto end;
+       }
+
+       ret = translate_field_class(ctx);
+       if (ret) {
+               BT_LOGE_STR("Cannot translate dynamic array field class element.");
+               goto end;
+       }
+
+       cur_path_stack_pop(ctx);
+       update_parent_field_class_alignment(ctx, fc->base.base.alignment);
+
+end:
+       return ret;
+}
+
+static inline
+int translate_integer_field_class(struct ctx *ctx)
+{
+       struct fs_sink_ctf_field_class_int *fc =
+               fs_sink_ctf_field_class_int_create(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+
+       BT_ASSERT(fc);
+       append_to_parent_field_class(ctx, (void *) fc);
+       return 0;
+}
+
+static inline
+int translate_real_field_class(struct ctx *ctx)
+{
+       struct fs_sink_ctf_field_class_float *fc =
+               fs_sink_ctf_field_class_float_create(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+
+       BT_ASSERT(fc);
+       append_to_parent_field_class(ctx, (void *) fc);
+       return 0;
+}
+
+static inline
+int translate_string_field_class(struct ctx *ctx)
+{
+       struct fs_sink_ctf_field_class_string *fc =
+               fs_sink_ctf_field_class_string_create(
+                       cur_path_stack_top(ctx)->ir_fc,
+                       cur_path_stack_top(ctx)->index_in_parent);
+
+       BT_ASSERT(fc);
+       append_to_parent_field_class(ctx, (void *) fc);
+       return 0;
+}
+
+/*
+ * Translates a field class, recursively.
+ *
+ * The field class's IR field class, parent field class, and index
+ * within its parent are in the context's current path's top element
+ * (cur_path_stack_top()).
+ */
+static
+int translate_field_class(struct ctx *ctx)
+{
+       int ret;
+
+       switch (bt_field_class_get_type(cur_path_stack_top(ctx)->ir_fc)) {
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER:
+       case BT_FIELD_CLASS_TYPE_SIGNED_INTEGER:
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION:
+       case BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION:
+               ret = translate_integer_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_REAL:
+               ret = translate_real_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_STRING:
+               ret = translate_string_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_STRUCTURE:
+               ret = translate_structure_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_STATIC_ARRAY:
+               ret = translate_static_array_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY:
+               ret = translate_dynamic_array_field_class(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_VARIANT:
+               ret = translate_variant_field_class(ctx);
+               break;
+       default:
+               abort();
+       }
+
+       return ret;
+}
+
+static
+int set_field_ref(struct fs_sink_ctf_field_class *fc, const char *fc_name,
+               struct fs_sink_ctf_field_class *parent_fc)
+{
+       int ret = 0;
+       GString *field_ref = NULL;
+       bool is_before;
+       const char *tgt_type;
+       struct fs_sink_ctf_field_class_struct *parent_struct_fc =
+               (void *) parent_fc;
+       uint64_t i;
+       unsigned int suffix = 0;
+
+       if (!fc_name || !parent_fc ||
+                       parent_fc->type != FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT) {
+               /* Not supported */
+               ret = -1;
+               goto end;
+       }
+
+       switch (fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+       {
+               struct fs_sink_ctf_field_class_sequence *seq_fc = (void *) fc;
+
+               field_ref = seq_fc->length_ref;
+               is_before = seq_fc->length_is_before;
+               tgt_type = "len";
+               break;
+       }
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+       {
+               struct fs_sink_ctf_field_class_variant *var_fc = (void *) fc;
+
+               field_ref = var_fc->tag_ref;
+               is_before = var_fc->tag_is_before;
+               tgt_type = "tag";
+               break;
+       }
+       default:
+               abort();
+       }
+
+       BT_ASSERT(field_ref);
+
+       if (!is_before) {
+               goto end;
+       }
+
+       /* Initial field ref */
+       g_string_printf(field_ref, "__%s_%s", fc_name, tgt_type);
+
+       /*
+        * Make sure field ref does not clash with an existing field
+        * class name within the same parent structure field class.
+        */
+       while (true) {
+               bool name_ok = true;
+
+               for (i = 0; i < parent_struct_fc->members->len; i++) {
+                       struct fs_sink_ctf_named_field_class *named_fc =
+                               fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                                       parent_struct_fc, i);
+
+                       if (strcmp(field_ref->str, named_fc->name->str) == 0) {
+                               /* Name clash */
+                               name_ok = false;
+                               break;
+                       }
+               }
+
+               if (name_ok) {
+                       /* No clash: we're done */
+                       break;
+               }
+
+               /* Append suffix and try again */
+               g_string_printf(field_ref, "__%s_%s_%u", fc_name, tgt_type,
+                       suffix);
+               suffix++;
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * This function recursively sets field refs of sequence and variant
+ * field classes when they are immediately before, avoiding name clashes
+ * with existing field class names.
+ *
+ * It can fail at this point if, for example, a sequence field class of
+ * which to set the length's field ref has something else than a
+ * structure field class as its parent: in this case, there's no
+ * location to place the length field class immediately before the
+ * sequence field class.
+ */
+static
+int set_field_refs(struct fs_sink_ctf_field_class *fc, const char *fc_name,
+               struct fs_sink_ctf_field_class *parent_fc)
+{
+       int ret = 0;
+       BT_ASSERT(fc);
+
+       switch (fc->type) {
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT:
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_VARIANT:
+       {
+               uint64_t i;
+               uint64_t len;
+               struct fs_sink_ctf_field_class_struct *struct_fc;
+               struct fs_sink_ctf_field_class_variant *var_fc;
+               struct fs_sink_ctf_named_field_class *named_fc;
+
+               if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT) {
+                       struct_fc = (void *) fc;
+                       len = struct_fc->members->len;
+               } else {
+                       var_fc = (void *) fc;
+                       len = var_fc->options->len;
+                       ret = set_field_ref(fc, fc_name, parent_fc);
+                       if (ret) {
+                               goto end;
+                       }
+               }
+
+               for (i = 0; i < len; i++) {
+                       if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_STRUCT) {
+                               named_fc = fs_sink_ctf_field_class_struct_borrow_member_by_index(
+                                       struct_fc, i);
+                       } else {
+                               named_fc = fs_sink_ctf_field_class_variant_borrow_option_by_index(
+                                       var_fc, i);
+                       }
+
+                       ret = set_field_refs(named_fc->fc, named_fc->name->str,
+                               fc);
+                       if (ret) {
+                               goto end;
+                       }
+               }
+
+               break;
+       }
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_ARRAY:
+       case FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE:
+       {
+               struct fs_sink_ctf_field_class_array_base *array_base_fc =
+                       (void *) fc;
+
+               if (fc->type == FS_SINK_CTF_FIELD_CLASS_TYPE_SEQUENCE) {
+                       ret = set_field_ref(fc, fc_name, parent_fc);
+                       if (ret) {
+                               goto end;
+                       }
+               }
+
+               ret = set_field_refs(array_base_fc->elem_fc, NULL, fc);
+               if (ret) {
+                       goto end;
+               }
+
+               break;
+       }
+       default:
+               break;
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * This function translates a root scope trace IR field class to
+ * a CTF IR field class.
+ *
+ * The resulting CTF IR field class is written to `*fc` so that it
+ * exists as the parent object's (stream class or event class) true root
+ * field class during the recursive translation for resolving purposes.
+ * This is also why this function creates the empty structure field
+ * class and then calls translate_structure_field_class_members() to
+ * fill it.
+ */
+static
+int translate_scope_field_class(struct ctx *ctx, bt_scope scope,
+               struct fs_sink_ctf_field_class **fc,
+               const bt_field_class *ir_fc)
+{
+       int ret = 0;
+
+       if (!ir_fc) {
+               goto end;
+       }
+
+       BT_ASSERT(bt_field_class_get_type(ir_fc) ==
+                       BT_FIELD_CLASS_TYPE_STRUCTURE);
+       BT_ASSERT(fc);
+       *fc = (void *) fs_sink_ctf_field_class_struct_create_empty(
+               ir_fc, UINT64_C(-1));
+       BT_ASSERT(*fc);
+       ctx->cur_scope = scope;
+       BT_ASSERT(ctx->cur_path->len == 0);
+       ret = cur_path_stack_push(ctx, UINT64_C(-1), NULL, ir_fc, NULL);
+       if (ret) {
+               BT_LOGE("Cannot translate scope structure field class: "
+                       "scope=%d", scope);
+               goto end;
+       }
+
+       ret = translate_structure_field_class_members(ctx, (void *) *fc, ir_fc);
+       if (ret) {
+               BT_LOGE("Cannot translate scope structure field class: "
+                       "scope=%d", scope);
+               goto end;
+       }
+
+       cur_path_stack_pop(ctx);
+
+       /* Set field refs for preceding targets */
+       ret = set_field_refs(*fc, NULL, NULL);
+
+end:
+       return ret;
+}
+
+static inline
+void ctx_init(struct ctx *ctx)
+{
+       memset(ctx, 0, sizeof(struct ctx));
+       ctx->cur_path = g_array_new(FALSE, TRUE,
+               sizeof(struct field_path_elem));
+       BT_ASSERT(ctx->cur_path);
+}
+
+static inline
+void ctx_fini(struct ctx *ctx)
+{
+       if (ctx->cur_path) {
+               g_array_free(ctx->cur_path, TRUE);
+               ctx->cur_path = NULL;
+       }
+}
+
+static
+int translate_event_class(struct fs_sink_ctf_stream_class *sc,
+               const bt_event_class *ir_ec,
+               struct fs_sink_ctf_event_class **out_ec)
+{
+       int ret = 0;
+       struct ctx ctx;
+       struct fs_sink_ctf_event_class *ec;
+
+       BT_ASSERT(sc);
+       BT_ASSERT(ir_ec);
+
+       ctx_init(&ctx);
+       ec = fs_sink_ctf_event_class_create(sc, ir_ec);
+       BT_ASSERT(ec);
+       ctx.cur_sc = sc;
+       ctx.cur_ec = ec;
+       ret = translate_scope_field_class(&ctx, BT_SCOPE_EVENT_SPECIFIC_CONTEXT,
+               &ec->spec_context_fc,
+               bt_event_class_borrow_specific_context_field_class_const(
+                       ir_ec));
+       if (ret) {
+               goto end;
+       }
+
+       ret = translate_scope_field_class(&ctx, BT_SCOPE_EVENT_PAYLOAD,
+               &ec->payload_fc,
+               bt_event_class_borrow_payload_field_class_const(ir_ec));
+       if (ret) {
+               goto end;
+       }
+
+end:
+       ctx_fini(&ctx);
+       *out_ec = ec;
+       return ret;
+}
+
+BT_HIDDEN
+int try_translate_event_class_trace_ir_to_ctf_ir(
+               struct fs_sink_ctf_stream_class *sc,
+               const bt_event_class *ir_ec,
+               struct fs_sink_ctf_event_class **out_ec)
+{
+       int ret = 0;
+
+       BT_ASSERT(sc);
+       BT_ASSERT(ir_ec);
+
+       /* Check in hash table first */
+       *out_ec = g_hash_table_lookup(sc->event_classes_from_ir, ir_ec);
+       if (likely(*out_ec)) {
+               goto end;
+       }
+
+       ret = translate_event_class(sc, ir_ec, out_ec);
+
+end:
+       return ret;
+}
+
+bool default_clock_class_name_exists(struct fs_sink_ctf_trace_class *tc,
+               const char *name)
+{
+       bool exists = false;
+       uint64_t i;
+
+       for (i = 0; i < tc->stream_classes->len; i++) {
+               struct fs_sink_ctf_stream_class *sc =
+                       tc->stream_classes->pdata[i];
+
+               if (sc->default_clock_class_name->len == 0) {
+                       /* No default clock class */
+                       continue;
+               }
+
+               if (strcmp(sc->default_clock_class_name->str, name) == 0) {
+                       exists = true;
+                       goto end;
+               }
+       }
+
+end:
+       return exists;
+}
+
+static
+void make_unique_default_clock_class_name(struct fs_sink_ctf_stream_class *sc)
+{
+       unsigned int suffix = 0;
+       char buf[16];
+
+       g_string_assign(sc->default_clock_class_name, "");
+       sprintf(buf, "default");
+
+       while (default_clock_class_name_exists(sc->tc, buf)) {
+               sprintf(buf, "default%u", suffix);
+               suffix++;
+       }
+
+       g_string_assign(sc->default_clock_class_name, buf);
+}
+
+static
+int translate_stream_class(struct fs_sink_ctf_trace_class *tc,
+               const bt_stream_class *ir_sc,
+               struct fs_sink_ctf_stream_class **out_sc)
+{
+       int ret = 0;
+       struct ctx ctx;
+
+       BT_ASSERT(tc);
+       BT_ASSERT(ir_sc);
+       ctx_init(&ctx);
+       *out_sc = fs_sink_ctf_stream_class_create(tc, ir_sc);
+       BT_ASSERT(*out_sc);
+
+       /* Set default clock class's protected name, if any */
+       if ((*out_sc)->default_clock_class) {
+               const char *name = bt_clock_class_get_name(
+                       (*out_sc)->default_clock_class);
+
+               if (!bt_stream_class_default_clock_is_always_known(ir_sc)) {
+                       BT_LOGE("Unsupported stream clock which can have an unknown value: "
+                               "sc-name=\"%s\"",
+                               bt_stream_class_get_name(ir_sc));
+                       goto error;
+               }
+
+               if (name) {
+                       /* Try original name, protected */
+                       g_string_assign((*out_sc)->default_clock_class_name,
+                               name);
+                       ret = fs_sink_ctf_protect_name(
+                               (*out_sc)->default_clock_class_name);
+                       if (ret) {
+                               /* Invalid: create a new name */
+                               make_unique_default_clock_class_name(*out_sc);
+                               ret = 0;
+                       }
+               } else {
+                       /* No name: create a name */
+                       make_unique_default_clock_class_name(*out_sc);
+               }
+       }
+
+       ctx.cur_sc = *out_sc;
+       ret = translate_scope_field_class(&ctx, BT_SCOPE_PACKET_CONTEXT,
+               &(*out_sc)->packet_context_fc,
+               bt_stream_class_borrow_packet_context_field_class_const(ir_sc));
+       if (ret) {
+               goto error;
+       }
+
+       if ((*out_sc)->packet_context_fc) {
+               /*
+                * Make sure the structure field class's alignment is
+                * enough: 8 is what we use for our own special members
+                * in the packet context.
+                */
+               fs_sink_ctf_field_class_struct_align_at_least(
+                       (void *) (*out_sc)->packet_context_fc, 8);
+       }
+
+       ret = translate_scope_field_class(&ctx, BT_SCOPE_EVENT_COMMON_CONTEXT,
+               &(*out_sc)->event_common_context_fc,
+               bt_stream_class_borrow_event_common_context_field_class_const(
+                       ir_sc));
+       if (ret) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       fs_sink_ctf_stream_class_destroy(*out_sc);
+       *out_sc = NULL;
+
+end:
+       ctx_fini(&ctx);
+       return ret;
+}
+
+BT_HIDDEN
+int try_translate_stream_class_trace_ir_to_ctf_ir(
+               struct fs_sink_ctf_trace_class *tc,
+               const bt_stream_class *ir_sc,
+               struct fs_sink_ctf_stream_class **out_sc)
+{
+       int ret = 0;
+       uint64_t i;
+
+       BT_ASSERT(tc);
+       BT_ASSERT(ir_sc);
+
+       for (i = 0; i < tc->stream_classes->len; i++) {
+               *out_sc = tc->stream_classes->pdata[i];
+
+               if ((*out_sc)->ir_sc == ir_sc) {
+                       goto end;
+               }
+       }
+
+       ret = translate_stream_class(tc, ir_sc, out_sc);
+
+end:
+       return ret;
+}
+
+BT_HIDDEN
+struct fs_sink_ctf_trace_class *translate_trace_class_trace_ir_to_ctf_ir(
+               const bt_trace_class *ir_tc)
+{
+       uint64_t count;
+       uint64_t i;
+       struct fs_sink_ctf_trace_class *tc = NULL;
+
+       /* Check that trace class's environment is TSDL-compatible */
+       count = bt_trace_class_get_environment_entry_count(ir_tc);
+       for (i = 0; i < count; i++) {
+               const char *name;
+               const bt_value *val;
+
+               bt_trace_class_borrow_environment_entry_by_index_const(
+                       ir_tc, i, &name, &val);
+
+               if (!fs_sink_ctf_ist_valid_identifier(name)) {
+                       BT_LOGE("Unsupported trace class's environment entry name: "
+                               "name=\"%s\"", name);
+                       goto end;
+               }
+
+               switch (bt_value_get_type(val)) {
+               case BT_VALUE_TYPE_INTEGER:
+               case BT_VALUE_TYPE_STRING:
+                       break;
+               default:
+                       BT_LOGE("Unsupported trace class's environment entry value type: "
+                               "type=%s",
+                               bt_common_value_type_string(
+                                       bt_value_get_type(val)));
+                       goto end;
+               }
+       }
+
+       tc = fs_sink_ctf_trace_class_create(ir_tc);
+       BT_ASSERT(tc);
+
+end:
+       return tc;
+}
diff --git a/plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.h b/plugins/ctf/fs-sink/translate-trace-ir-to-ctf-ir.h
new file mode 100644 (file)
index 0000000..3787237
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_TRACE_IR_TO_CTF_IR_H
+#define BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_TRACE_IR_TO_CTF_IR_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/babeltrace.h>
+
+#include "fs-sink-ctf-meta.h"
+
+BT_HIDDEN
+int try_translate_event_class_trace_ir_to_ctf_ir(
+               struct fs_sink_ctf_stream_class *sc,
+               const bt_event_class *ir_ec,
+               struct fs_sink_ctf_event_class **out_ec);
+
+BT_HIDDEN
+int try_translate_stream_class_trace_ir_to_ctf_ir(
+               struct fs_sink_ctf_trace_class *tc,
+               const bt_stream_class *ir_sc,
+               struct fs_sink_ctf_stream_class **out_sc);
+
+BT_HIDDEN
+struct fs_sink_ctf_trace_class *translate_trace_class_trace_ir_to_ctf_ir(
+               const bt_trace_class *ir_tc);
+
+#endif /* BABELTRACE_PLUGIN_CTF_FS_SINK_TRANSLATE_TRACE_IR_TO_CTF_IR_H */
diff --git a/plugins/ctf/fs-sink/write.c b/plugins/ctf/fs-sink/write.c
deleted file mode 100644 (file)
index 82057e0..0000000
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * writer.c
- *
- * Babeltrace CTF Writer Output Plugin Event Handling
- *
- * Copyright 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Author: Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-WRITE"
-#include "logging.h"
-
-#include <babeltrace/babeltrace.h>
-#include <babeltrace/assert-internal.h>
-#include <glib.h>
-
-#include <ctfcopytrace.h>
-
-#include "writer.h"
-
-static
-void unref_stream_class(const bt_stream_class *writer_stream_class)
-{
-       bt_stream_class_put_ref(writer_stream_class);
-}
-
-static
-void unref_stream(const bt_stream_class *writer_stream)
-{
-       bt_stream_class_put_ref(writer_stream);
-}
-
-static
-gboolean empty_ht(gpointer key, gpointer value, gpointer user_data)
-{
-       return TRUE;
-}
-
-static
-gboolean empty_streams_ht(gpointer key, gpointer value, gpointer user_data)
-{
-       int ret;
-       const bt_stream *writer_stream = value;
-
-       ret = bt_stream_flush(writer_stream);
-       if (ret) {
-               BT_LOGD_STR("Failed to flush stream while emptying hash table.");
-       }
-       return TRUE;
-}
-
-static
-void destroy_stream_state_key(gpointer key)
-{
-       g_free((enum fs_writer_stream_state *) key);
-}
-
-static
-void check_completed_trace(gpointer key, gpointer value, gpointer user_data)
-{
-       enum fs_writer_stream_state *state = value;
-       int *trace_completed = user_data;
-
-       if (*state != FS_WRITER_COMPLETED_STREAM) {
-               *trace_completed = 0;
-       }
-}
-
-static
-void trace_is_static_listener(const bt_trace *trace, void *data)
-{
-       struct fs_writer *fs_writer = data;
-       int trace_completed = 1;
-
-       fs_writer->trace_static = 1;
-
-       g_hash_table_foreach(fs_writer->stream_states,
-                       check_completed_trace, &trace_completed);
-       if (trace_completed) {
-               writer_close(fs_writer->writer_component, fs_writer);
-               g_hash_table_remove(fs_writer->writer_component->trace_map,
-                               fs_writer->trace);
-       }
-}
-
-static
-const bt_stream_class *insert_new_stream_class(
-               struct writer_component *writer_component,
-               struct fs_writer *fs_writer,
-               const bt_stream_class *stream_class)
-{
-       const bt_stream_class *writer_stream_class = NULL;
-       const bt_trace *trace = NULL, *writer_trace = NULL;
-       struct bt_ctf_writer *ctf_writer = fs_writer->writer;
-       bt_component_status ret;
-
-       trace = bt_stream_class_get_trace(stream_class);
-       BT_ASSERT(trace);
-
-       writer_trace = bt_ctf_writer_get_trace(ctf_writer);
-       BT_ASSERT(writer_trace);
-
-       ret = ctf_copy_clock_classes(writer_component->err, writer_trace,
-                       writer_stream_class, trace);
-       if (ret != BT_COMPONENT_STATUS_OK) {
-               BT_LOGE_STR("Failed to copy clock classes.");
-               goto error;
-       }
-
-       writer_stream_class = ctf_copy_stream_class(writer_component->err,
-                       stream_class, writer_trace, true);
-       if (!writer_stream_class) {
-               BT_LOGE_STR("Failed to copy stream class.");
-               goto error;
-       }
-
-       ret = bt_trace_add_stream_class(writer_trace, writer_stream_class);
-       if (ret) {
-               BT_LOGE_STR("Failed to add stream_class.");
-               goto error;
-       }
-
-       g_hash_table_insert(fs_writer->stream_class_map,
-                       (gpointer) stream_class, writer_stream_class);
-
-       goto end;
-
-error:
-       BT_STREAM_CLASS_PUT_REF_AND_RESET(writer_stream_class);
-end:
-       bt_trace_put_ref(writer_trace);
-       bt_trace_put_ref(trace);
-       return writer_stream_class;
-}
-
-static
-enum fs_writer_stream_state *insert_new_stream_state(
-               struct writer_component *writer_component,
-               struct fs_writer *fs_writer, const bt_stream *stream)
-{
-       enum fs_writer_stream_state *v = NULL;
-
-       v = g_new0(enum fs_writer_stream_state, 1);
-       if (!v) {
-               BT_LOGE_STR("Failed to allocate fs_writer_stream_state.");
-               goto end;
-       }
-       *v = FS_WRITER_UNKNOWN_STREAM;
-
-       g_hash_table_insert(fs_writer->stream_states, stream, v);
-end:
-       return v;
-}
-
-/*
- * Make sure the output path is valid for a single trace: either it does
- * not exists or it is empty.
- *
- * Return 0 if the path is valid, -1 otherwise.
- */
-static
-bool valid_single_trace_path(const char *path)
-{
-       GError *error = NULL;
-       GDir *dir = NULL;
-       int ret = 0;
-
-       dir = g_dir_open(path, 0, &error);
-
-       /* Non-existent directory. */
-       if (!dir) {
-               /* For any other error, return an error */
-               if (error->code != G_FILE_ERROR_NOENT) {
-                       ret = -1;
-               }
-               goto end;
-       }
-
-       /* g_dir_read_name skips "." and "..", error out on first result */
-       while (g_dir_read_name(dir) != NULL) {
-               ret = -1;
-               break;
-       }
-
-end:
-       if (dir) {
-               g_dir_close(dir);
-       }
-       if (error) {
-               g_error_free(error);
-       }
-
-       return ret;
-}
-
-static
-int make_trace_path(struct writer_component *writer_component,
-               const bt_trace *trace, char *trace_path)
-{
-       int ret;
-       const char *trace_name;
-
-       if (writer_component->single_trace) {
-               trace_name = "\0";
-       } else {
-               trace_name = bt_trace_get_name(trace);
-               if (!trace_name) {
-                       trace_name = writer_component->trace_name_base->str;
-               }
-       }
-
-       /* Sanitize the trace name. */
-       if (strlen(trace_name) == 2 && !strcmp(trace_name, "..")) {
-               BT_LOGE_STR("Trace name cannot be \"..\".");
-               goto error;
-       }
-
-       if (strstr(trace_name, "../")) {
-               BT_LOGE_STR("Trace name cannot contain \"../\".");
-               goto error;
-
-       }
-
-       snprintf(trace_path, PATH_MAX, "%s" G_DIR_SEPARATOR_S "%s",
-                       writer_component->base_path->str,
-                       trace_name);
-       /*
-        * Append a suffix if the trace_path exists and we are not in
-        * single-trace mode.
-        */
-       if (writer_component->single_trace) {
-               if (valid_single_trace_path(trace_path) != 0) {
-                       BT_LOGE_STR("Invalid output directory.");
-                       goto error;
-               }
-       } else {
-               if (g_file_test(trace_path, G_FILE_TEST_EXISTS)) {
-                       int i = 0;
-
-                       do {
-                               snprintf(trace_path, PATH_MAX, "%s" G_DIR_SEPARATOR_S "%s-%d",
-                                               writer_component->base_path->str,
-                                               trace_name, ++i);
-                       } while (g_file_test(trace_path, G_FILE_TEST_EXISTS) && i < INT_MAX);
-                       if (i == INT_MAX) {
-                               BT_LOGE_STR("Unable to find a unique trace path.");
-                               goto error;
-                       }
-               }
-       }
-
-       ret = 0;
-       goto end;
-
-error:
-       ret = -1;
-end:
-       return ret;
-}
-
-static
-struct fs_writer *insert_new_writer(
-               struct writer_component *writer_component,
-               const bt_trace *trace)
-{
-       struct bt_ctf_writer *ctf_writer = NULL;
-       const bt_trace *writer_trace = NULL;
-       char trace_path[PATH_MAX];
-       bt_component_status ret;
-       const bt_stream *stream = NULL;
-       struct fs_writer *fs_writer = NULL;
-       int nr_stream, i;
-
-       if (writer_component->single_trace && writer_component->nr_traces > 0) {
-               BT_LOGE_STR("Trying to process more than one trace but single trace mode enabled.");
-               goto error;
-       }
-
-       ret = make_trace_path(writer_component, trace, trace_path);
-       if (ret) {
-               BT_LOGE_STR("Failed to make trace path.");
-               goto error;
-       }
-
-       printf("ctf.fs sink creating trace in %s\n", trace_path);
-
-       ctf_writer = bt_ctf_writer_create(trace_path);
-       if (!ctf_writer) {
-               BT_LOGE_STR("Failed to create CTF writer.");
-               goto error;
-       }
-
-       writer_trace = bt_ctf_writer_get_trace(ctf_writer);
-       BT_ASSERT(writer_trace);
-
-       ret = ctf_copy_trace(writer_component->err, trace, writer_trace);
-       if (ret != BT_COMPONENT_STATUS_OK) {
-               BT_LOGE_STR("Failed to copy trace.");
-               BT_OBJECT_PUT_REF_AND_RESET(ctf_writer);
-               goto error;
-       }
-
-       fs_writer = g_new0(struct fs_writer, 1);
-       if (!fs_writer) {
-               BT_LOGE_STR("Failed to allocate fs_writer.");
-               goto error;
-       }
-       fs_writer->writer = ctf_writer;
-       fs_writer->trace = trace;
-       fs_writer->writer_trace = writer_trace;
-       fs_writer->writer_component = writer_component;
-       BT_TRACE_PUT_REF_AND_RESET(writer_trace);
-       fs_writer->stream_class_map = g_hash_table_new_full(g_direct_hash,
-                       g_direct_equal, NULL, (GDestroyNotify) unref_stream_class);
-       fs_writer->stream_map = g_hash_table_new_full(g_direct_hash,
-                       g_direct_equal, NULL, (GDestroyNotify) unref_stream);
-       fs_writer->stream_states = g_hash_table_new_full(g_direct_hash,
-                       g_direct_equal, NULL, destroy_stream_state_key);
-
-       /* Set all the existing streams in the unknown state. */
-       nr_stream = bt_trace_get_stream_count(trace);
-       for (i = 0; i < nr_stream; i++) {
-               stream = bt_trace_get_stream_by_index(trace, i);
-               BT_ASSERT(stream);
-
-               insert_new_stream_state(writer_component, fs_writer, stream);
-               BT_STREAM_PUT_REF_AND_RESET(stream);
-       }
-
-       /* Check if the trace is already static or register a listener. */
-       if (bt_trace_is_static(trace)) {
-               fs_writer->trace_static = 1;
-               fs_writer->static_listener_id = -1;
-       } else {
-               ret = bt_trace_add_is_static_listener(trace,
-                               trace_is_static_listener, NULL, fs_writer);
-               BT_ASSERT(ret >= 0);
-               fs_writer->static_listener_id = ret;
-       }
-
-       writer_component->nr_traces++;
-       g_hash_table_insert(writer_component->trace_map, (gpointer) trace,
-                       fs_writer);
-
-       goto end;
-
-error:
-       g_free(fs_writer);
-       fs_writer = NULL;
-       bt_trace_put_ref(writer_trace);
-       bt_stream_put_ref(stream);
-       BT_OBJECT_PUT_REF_AND_RESET(ctf_writer);
-end:
-       return fs_writer;
-}
-
-static
-struct fs_writer *get_fs_writer(struct writer_component *writer_component,
-               const bt_stream_class *stream_class)
-{
-       const bt_trace *trace = NULL;
-       struct fs_writer *fs_writer;
-
-       trace = bt_stream_class_get_trace(stream_class);
-       BT_ASSERT(trace);
-
-       fs_writer = g_hash_table_lookup(writer_component->trace_map,
-                       (gpointer) trace);
-       if (!fs_writer) {
-               fs_writer = insert_new_writer(writer_component, trace);
-       }
-       BT_TRACE_PUT_REF_AND_RESET(trace);
-
-       return fs_writer;
-}
-
-static
-struct fs_writer *get_fs_writer_from_stream(
-               struct writer_component *writer_component,
-               const bt_stream *stream)
-{
-       const bt_stream_class *stream_class = NULL;
-       struct fs_writer *fs_writer;
-
-       stream_class = bt_stream_get_class(stream);
-       BT_ASSERT(stream_class);
-
-       fs_writer = get_fs_writer(writer_component, stream_class);
-
-       bt_stream_class_put_ref(stream_class);
-       return fs_writer;
-}
-
-static
-const bt_stream_class *lookup_stream_class(
-               struct writer_component *writer_component,
-               const bt_stream_class *stream_class)
-{
-       struct fs_writer *fs_writer = get_fs_writer(
-                       writer_component, stream_class);
-       BT_ASSERT(fs_writer);
-       return (const bt_stream_class *) g_hash_table_lookup(
-                       fs_writer->stream_class_map, (gpointer) stream_class);
-}
-
-static
-const bt_stream *lookup_stream(struct writer_component *writer_component,
-               const bt_stream *stream)
-{
-       struct fs_writer *fs_writer = get_fs_writer_from_stream(
-                       writer_component, stream);
-       BT_ASSERT(fs_writer);
-       return (const bt_stream *) g_hash_table_lookup(
-                       fs_writer->stream_map, (gpointer) stream);
-}
-
-static
-const bt_stream *insert_new_stream(
-               struct writer_component *writer_component,
-               struct fs_writer *fs_writer,
-               const bt_stream_class *stream_class,
-               const bt_stream *stream)
-{
-       const bt_stream *writer_stream = NULL;
-       const bt_stream_class *writer_stream_class = NULL;
-       struct bt_ctf_writer *ctf_writer = bt_object_get_ref(fs_writer->writer);
-
-       writer_stream_class = lookup_stream_class(writer_component,
-                       stream_class);
-       if (!writer_stream_class) {
-               writer_stream_class = insert_new_stream_class(
-                               writer_component, fs_writer, stream_class);
-               if (!writer_stream_class) {
-                       BT_LOGE_STR("Failed to insert a new stream_class.");
-                       goto error;
-               }
-       }
-       bt_stream_class_get_ref(writer_stream_class);
-
-       writer_stream = bt_stream_create(writer_stream_class,
-               bt_stream_get_name(stream));
-       BT_ASSERT(writer_stream);
-
-       g_hash_table_insert(fs_writer->stream_map, (gpointer) stream,
-                       writer_stream);
-
-       goto end;
-
-error:
-       BT_STREAM_PUT_REF_AND_RESET(writer_stream);
-end:
-       bt_object_put_ref(ctf_writer);
-       bt_stream_class_put_ref(writer_stream_class);
-       return writer_stream;
-}
-
-static
-const bt_event_class *get_event_class(struct writer_component *writer_component,
-               const bt_stream_class *writer_stream_class,
-               const bt_event_class *event_class)
-{
-       return bt_stream_class_get_event_class_by_id(writer_stream_class,
-                       bt_event_class_get_id(event_class));
-}
-
-static
-const bt_stream *get_writer_stream(
-               struct writer_component *writer_component,
-               const bt_packet *packet, const bt_stream *stream)
-{
-       const bt_stream *writer_stream = NULL;
-
-       writer_stream = lookup_stream(writer_component, stream);
-       if (!writer_stream) {
-               BT_LOGE_STR("Failed to find existing stream.");
-               goto error;
-       }
-       bt_stream_get_ref(writer_stream);
-
-       goto end;
-
-error:
-       BT_STREAM_PUT_REF_AND_RESET(writer_stream);
-end:
-       return writer_stream;
-}
-
-BT_HIDDEN
-void writer_close(struct writer_component *writer_component,
-               struct fs_writer *fs_writer)
-{
-       if (fs_writer->static_listener_id >= 0) {
-               bt_trace_remove_is_static_listener(fs_writer->trace,
-                               fs_writer->static_listener_id);
-       }
-
-       /* Empty the stream class HT. */
-       g_hash_table_foreach_remove(fs_writer->stream_class_map,
-                       empty_ht, NULL);
-       g_hash_table_destroy(fs_writer->stream_class_map);
-
-       /* Empty the stream HT. */
-       g_hash_table_foreach_remove(fs_writer->stream_map,
-                       empty_streams_ht, NULL);
-       g_hash_table_destroy(fs_writer->stream_map);
-
-       /* Empty the stream state HT. */
-       g_hash_table_foreach_remove(fs_writer->stream_states,
-                       empty_ht, NULL);
-       g_hash_table_destroy(fs_writer->stream_states);
-}
-
-BT_HIDDEN
-bt_component_status writer_stream_begin(
-               struct writer_component *writer_component,
-               const bt_stream *stream)
-{
-       const bt_stream_class *stream_class = NULL;
-       struct fs_writer *fs_writer;
-       const bt_stream *writer_stream = NULL;
-       bt_component_status ret = BT_COMPONENT_STATUS_OK;
-       enum fs_writer_stream_state *state;
-
-       stream_class = bt_stream_get_class(stream);
-       BT_ASSERT(stream_class);
-
-       fs_writer = get_fs_writer(writer_component, stream_class);
-       if (!fs_writer) {
-               BT_LOGE_STR("Failed to get fs_writer.");
-               goto error;
-       }
-
-       /* Set the stream as active */
-       state = g_hash_table_lookup(fs_writer->stream_states, stream);
-       if (!state) {
-               if (fs_writer->trace_static) {
-                       BT_LOGE_STR("Cannot add new stream on a static trace.");
-                       goto error;
-               }
-               state = insert_new_stream_state(writer_component, fs_writer,
-                               stream);
-       }
-       if (*state != FS_WRITER_UNKNOWN_STREAM) {
-               BT_LOGE("Unexpected stream state: state=%d", *state);
-               goto error;
-       }
-       *state = FS_WRITER_ACTIVE_STREAM;
-
-       writer_stream = insert_new_stream(writer_component, fs_writer,
-                       stream_class, stream);
-       if (!writer_stream) {
-               BT_LOGE_STR("Failed to insert new stream.");
-               goto error;
-       }
-
-       goto end;
-
-error:
-       ret = BT_COMPONENT_STATUS_ERROR;
-end:
-       bt_object_put_ref(stream_class);
-       return ret;
-}
-
-BT_HIDDEN
-bt_component_status writer_stream_end(
-               struct writer_component *writer_component,
-               const bt_stream *stream)
-{
-       const bt_stream_class *stream_class = NULL;
-       struct fs_writer *fs_writer;
-       const bt_trace *trace = NULL;
-       bt_component_status ret = BT_COMPONENT_STATUS_OK;
-       enum fs_writer_stream_state *state;
-
-       stream_class = bt_stream_get_class(stream);
-       BT_ASSERT(stream_class);
-
-       fs_writer = get_fs_writer(writer_component, stream_class);
-       if (!fs_writer) {
-               BT_LOGE_STR("Failed to get fs_writer.");
-               goto error;
-       }
-
-       state = g_hash_table_lookup(fs_writer->stream_states, stream);
-       if (*state != FS_WRITER_ACTIVE_STREAM) {
-               BT_LOGE("Unexpected stream state: state=%d", *state);
-               goto error;
-       }
-       *state = FS_WRITER_COMPLETED_STREAM;
-
-       g_hash_table_remove(fs_writer->stream_map, stream);
-
-       if (fs_writer->trace_static) {
-               int trace_completed = 1;
-
-               g_hash_table_foreach(fs_writer->stream_states,
-                               check_completed_trace, &trace_completed);
-               if (trace_completed) {
-                       writer_close(writer_component, fs_writer);
-                       g_hash_table_remove(writer_component->trace_map,
-                                       fs_writer->trace);
-               }
-       }
-
-       goto end;
-
-error:
-       ret = BT_COMPONENT_STATUS_ERROR;
-end:
-       BT_OBJECT_PUT_REF_AND_RESET(trace);
-       BT_OBJECT_PUT_REF_AND_RESET(stream_class);
-       return ret;
-}
-
-BT_HIDDEN
-bt_component_status writer_new_packet(
-               struct writer_component *writer_component,
-               const bt_packet *packet)
-{
-       const bt_stream *stream = NULL, *writer_stream = NULL;
-       bt_component_status ret = BT_COMPONENT_STATUS_OK;
-       int int_ret;
-
-       stream = bt_packet_get_stream(packet);
-       BT_ASSERT(stream);
-
-       writer_stream = get_writer_stream(writer_component, packet, stream);
-       if (!writer_stream) {
-               BT_LOGE_STR("Failed to get writer_stream.");
-               goto error;
-       }
-       BT_OBJECT_PUT_REF_AND_RESET(stream);
-
-       int_ret = ctf_stream_copy_packet_context(
-                       writer_component->err, packet, writer_stream);
-       if (int_ret < 0) {
-               BT_LOGE_STR("Failed to copy packet_context.");
-               goto error;
-       }
-
-       ret = ctf_stream_copy_packet_header(writer_component->err,
-                       packet, writer_stream);
-       if (ret != 0) {
-               BT_LOGE_STR("Failed to copy packet_header.");
-               goto error;
-       }
-
-       goto end;
-
-error:
-       ret = BT_COMPONENT_STATUS_ERROR;
-end:
-       bt_object_put_ref(writer_stream);
-       bt_object_put_ref(stream);
-       return ret;
-}
-
-BT_HIDDEN
-bt_component_status writer_close_packet(
-               struct writer_component *writer_component,
-               const bt_packet *packet)
-{
-       const bt_stream *stream = NULL, *writer_stream = NULL;
-       bt_component_status ret;
-
-       stream = bt_packet_get_stream(packet);
-       BT_ASSERT(stream);
-
-       writer_stream = lookup_stream(writer_component, stream);
-       if (!writer_stream) {
-               BT_LOGE_STR("Failed to find existing stream.");
-               goto error;
-       }
-       BT_OBJECT_PUT_REF_AND_RESET(stream);
-
-       bt_object_get_ref(writer_stream);
-
-       ret = bt_stream_flush(writer_stream);
-       if (ret) {
-               BT_LOGE_STR("Failed to flush stream.");
-               goto error;
-       }
-
-       BT_OBJECT_PUT_REF_AND_RESET(writer_stream);
-
-       ret = BT_COMPONENT_STATUS_OK;
-       goto end;
-
-error:
-       ret = BT_COMPONENT_STATUS_ERROR;
-end:
-       bt_object_put_ref(writer_stream);
-       bt_object_put_ref(stream);
-       return ret;
-}
-
-BT_HIDDEN
-bt_component_status writer_output_event(
-               struct writer_component *writer_component,
-               const bt_event *event)
-{
-       bt_component_status ret;
-       const bt_event_class *event_class = NULL, *writer_event_class = NULL;
-       const bt_stream *stream = NULL, *writer_stream = NULL;
-       const bt_stream_class *stream_class = NULL, *writer_stream_class = NULL;
-       const bt_event *writer_event = NULL;
-       int int_ret;
-       const bt_trace *writer_trace = NULL;
-
-       event_class = bt_event_get_class(event);
-       BT_ASSERT(event_class);
-
-       stream = bt_event_get_stream(event);
-       BT_ASSERT(stream);
-
-       writer_stream = lookup_stream(writer_component, stream);
-       if (!writer_stream || !bt_object_get_ref(writer_stream)) {
-               BT_LOGE_STR("Failed for find existing stream.");
-               goto error;
-       }
-
-       stream_class = bt_event_class_get_stream_class(event_class);
-       BT_ASSERT(stream_class);
-
-       writer_stream_class = lookup_stream_class(writer_component, stream_class);
-       if (!writer_stream_class || !bt_object_get_ref(writer_stream_class)) {
-               BT_LOGE_STR("Failed to find existing stream_class.");
-               goto error;
-       }
-
-       writer_trace = bt_stream_class_get_trace(writer_stream_class);
-       BT_ASSERT(writer_trace);
-
-       writer_event_class = get_event_class(writer_component,
-                       writer_stream_class, event_class);
-       if (!writer_event_class) {
-               writer_event_class = ctf_copy_event_class(writer_component->err,
-                               writer_trace, event_class);
-               if (!writer_event_class) {
-                       BT_LOGE_STR("Failed to copy event_class.");
-                       goto error;
-               }
-               int_ret = bt_stream_class_add_event_class(
-                               writer_stream_class, writer_event_class);
-               if (int_ret) {
-                       BT_LOGE("Failed to add event_class: event_name=\"%s\"",
-                                       bt_event_class_get_name(event_class));
-                       goto error;
-               }
-       }
-
-       writer_event = ctf_copy_event(writer_component->err, event,
-                       writer_event_class, true);
-       if (!writer_event) {
-               BT_LOGE("Failed to copy event: event_class=\"%s\"",
-                               bt_event_class_get_name(writer_event_class));
-               goto error;
-       }
-
-       int_ret = bt_stream_append_event(writer_stream, writer_event);
-       if (int_ret < 0) {
-               BT_LOGE("Failed to append event: event_class=\"%s\"",
-                               bt_event_class_get_name(writer_event_class));
-               goto error;
-       }
-
-       ret = BT_COMPONENT_STATUS_OK;
-       goto end;
-
-error:
-       ret = BT_COMPONENT_STATUS_ERROR;
-end:
-       bt_object_put_ref(writer_trace);
-       bt_object_put_ref(writer_event);
-       bt_object_put_ref(writer_event_class);
-       bt_object_put_ref(writer_stream_class);
-       bt_object_put_ref(stream_class);
-       bt_object_put_ref(writer_stream);
-       bt_object_put_ref(stream);
-       bt_object_put_ref(event_class);
-       return ret;
-}
diff --git a/plugins/ctf/fs-sink/writer.c b/plugins/ctf/fs-sink/writer.c
deleted file mode 100644 (file)
index 72e3ab6..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * writer.c
- *
- * Babeltrace CTF Writer Output Plugin
- *
- * Copyright 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Author: Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-WRITER"
-#include "logging.h"
-
-#include <babeltrace/babeltrace.h>
-#include <plugins-common.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <glib.h>
-#include "writer.h"
-#include <babeltrace/assert-internal.h>
-
-static
-gboolean empty_trace_map(gpointer key, gpointer value, gpointer user_data)
-{
-       struct fs_writer *fs_writer = value;
-       struct writer_component *writer_component = user_data;
-
-       fs_writer->trace_static = 1;
-       writer_close(writer_component, fs_writer);
-
-       return TRUE;
-}
-
-static
-void destroy_writer_component_data(struct writer_component *writer_component)
-{
-       bt_object_put_ref(writer_component->input_iterator);
-
-       g_hash_table_foreach_remove(writer_component->trace_map,
-                       empty_trace_map, writer_component);
-       g_hash_table_destroy(writer_component->trace_map);
-
-       g_string_free(writer_component->base_path, true);
-       g_string_free(writer_component->trace_name_base, true);
-}
-
-BT_HIDDEN
-void writer_component_finalize(bt_self_component *component)
-{
-       struct writer_component *writer_component = (struct writer_component *)
-               bt_self_component_get_user_data(component);
-
-       destroy_writer_component_data(writer_component);
-       g_free(writer_component);
-}
-
-static
-void free_fs_writer(struct fs_writer *fs_writer)
-{
-       bt_object_put_ref(fs_writer->writer);
-       g_free(fs_writer);
-}
-
-static
-struct writer_component *create_writer_component(void)
-{
-       struct writer_component *writer_component;
-
-       writer_component = g_new0(struct writer_component, 1);
-       if (!writer_component) {
-               goto end;
-       }
-
-       writer_component->err = stderr;
-       writer_component->trace_id = 0;
-       writer_component->trace_name_base = g_string_new("trace");
-       if (!writer_component->trace_name_base) {
-               g_free(writer_component);
-               writer_component = NULL;
-               goto end;
-       }
-
-       /*
-        * Reader to writer corresponding structures.
-        */
-       writer_component->trace_map = g_hash_table_new_full(g_direct_hash,
-                       g_direct_equal, NULL, (GDestroyNotify) free_fs_writer);
-
-end:
-       return writer_component;
-}
-
-static
-bt_component_status handle_message(
-               struct writer_component *writer_component,
-               const bt_message *message)
-{
-       bt_component_status ret = BT_COMPONENT_STATUS_OK;
-
-       if (!writer_component) {
-               ret = BT_COMPONENT_STATUS_ERROR;
-               goto end;
-       }
-
-       switch (bt_message_get_type(message)) {
-       case BT_MESSAGE_TYPE_PACKET_BEGINNING:
-       {
-               const bt_packet *packet =
-                       bt_message_packet_beginning_get_packet(message);
-
-               if (!packet) {
-                       ret = BT_COMPONENT_STATUS_ERROR;
-                       goto end;
-               }
-
-               ret = writer_new_packet(writer_component, packet);
-               bt_packet_put_ref(packet);
-               break;
-       }
-       case BT_MESSAGE_TYPE_PACKET_END:
-       {
-               const bt_packet *packet =
-                       bt_message_packet_end_get_packet(message);
-
-               if (!packet) {
-                       ret = BT_COMPONENT_STATUS_ERROR;
-                       goto end;
-               }
-               ret = writer_close_packet(writer_component, packet);
-               bt_packet_put_ref(packet);
-               break;
-       }
-       case BT_MESSAGE_TYPE_EVENT:
-       {
-               const bt_event *event = bt_message_event_get_event(
-                               message);
-
-               if (!event) {
-                       ret = BT_COMPONENT_STATUS_ERROR;
-                       goto end;
-               }
-               ret = writer_output_event(writer_component, event);
-               bt_object_put_ref(event);
-               if (ret != BT_COMPONENT_STATUS_OK) {
-                       goto end;
-               }
-               break;
-       }
-       case BT_MESSAGE_TYPE_STREAM_BEGINNING:
-       {
-               const bt_stream *stream =
-                       bt_message_stream_beginning_get_stream(message);
-
-               if (!stream) {
-                       ret = BT_COMPONENT_STATUS_ERROR;
-                       goto end;
-               }
-               ret = writer_stream_begin(writer_component, stream);
-               bt_stream_put_ref(stream);
-               break;
-       }
-       case BT_MESSAGE_TYPE_STREAM_END:
-       {
-               const bt_stream *stream =
-                       bt_message_stream_end_get_stream(message);
-
-               if (!stream) {
-                       ret = BT_COMPONENT_STATUS_ERROR;
-                       goto end;
-               }
-               ret = writer_stream_end(writer_component, stream);
-               bt_stream_put_ref(stream);
-               break;
-       }
-       default:
-               break;
-       }
-end:
-       return ret;
-}
-
-BT_HIDDEN
-void writer_component_port_connected(
-               bt_self_component *component,
-               struct bt_private_port *self_port,
-               const bt_port *other_port)
-{
-       struct bt_private_connection *connection;
-       struct writer_component *writer;
-       bt_connection_status conn_status;
-
-       writer = bt_self_component_get_user_data(component);
-       BT_ASSERT(writer);
-       BT_ASSERT(!writer->input_iterator);
-       connection = bt_private_port_get_connection(self_port);
-       BT_ASSERT(connection);
-       conn_status = bt_private_connection_create_message_iterator(
-               connection, &writer->input_iterator);
-       if (conn_status != BT_CONNECTION_STATUS_OK) {
-               writer->error = true;
-       }
-
-       bt_object_put_ref(connection);
-}
-
-BT_HIDDEN
-bt_component_status writer_run(bt_self_component *component)
-{
-       bt_component_status ret;
-       const bt_message *message = NULL;
-       bt_message_iterator *it;
-       struct writer_component *writer_component =
-               bt_self_component_get_user_data(component);
-       bt_message_iterator_status it_ret;
-
-       if (unlikely(writer_component->error)) {
-               ret = BT_COMPONENT_STATUS_ERROR;
-               goto end;
-       }
-
-       it = writer_component->input_iterator;
-       BT_ASSERT(it);
-       it_ret = bt_message_iterator_next(it);
-
-       switch (it_ret) {
-       case BT_MESSAGE_ITERATOR_STATUS_END:
-               ret = BT_COMPONENT_STATUS_END;
-               BT_OBJECT_PUT_REF_AND_RESET(writer_component->input_iterator);
-               goto end;
-       case BT_MESSAGE_ITERATOR_STATUS_AGAIN:
-               ret = BT_COMPONENT_STATUS_AGAIN;
-               goto end;
-       case BT_MESSAGE_ITERATOR_STATUS_OK:
-               break;
-       default:
-               ret = BT_COMPONENT_STATUS_ERROR;
-               goto end;
-       }
-
-       message = bt_message_iterator_get_message(it);
-       BT_ASSERT(message);
-       ret = handle_message(writer_component, message);
-end:
-       bt_object_put_ref(message);
-       return ret;
-}
-
-static
-bt_component_status apply_one_bool(const char *key,
-               bt_value *params,
-               bool *option,
-               bool *found)
-{
-       bt_component_status ret = BT_COMPONENT_STATUS_OK;
-       bt_value *value = NULL;
-       bt_value_status status;
-       bt_bool bool_val;
-
-       value = bt_value_map_get(params, key);
-       if (!value) {
-               goto end;
-       }
-       bool_val = bt_value_bool_get(value);
-
-       *option = (bool) bool_val;
-       if (found) {
-               *found = true;
-       }
-end:
-       bt_value_put_ref(value);
-       return ret;
-}
-
-BT_HIDDEN
-bt_component_status writer_component_init(
-       bt_self_component *component, bt_value *params,
-       UNUSED_VAR void *init_method_data)
-{
-       bt_component_status ret;
-       bt_value_status value_ret;
-       struct writer_component *writer_component = create_writer_component();
-       bt_value *value = NULL;
-       const char *path;
-
-       if (!writer_component) {
-               ret = BT_COMPONENT_STATUS_NOMEM;
-               goto end;
-       }
-
-       ret = bt_self_component_sink_add_input_port(component,
-               "in", NULL, NULL);
-       if (ret != BT_COMPONENT_STATUS_OK) {
-               goto end;
-       }
-
-       value = bt_value_map_get(params, "path");
-       if (!value || bt_value_is_null(value) || !bt_value_is_string(value)) {
-               BT_LOGE_STR("Missing mandatory \"path\" parameter.");
-               ret = BT_COMPONENT_STATUS_INVALID;
-               goto error;
-       }
-
-       value_ret = bt_value_string_get(value, &path);
-       if (value_ret != BT_VALUE_STATUS_OK) {
-               ret = BT_COMPONENT_STATUS_INVALID;
-               goto error;
-       }
-       bt_object_put_ref(value);
-
-       writer_component->base_path = g_string_new(path);
-       if (!writer_component->base_path) {
-               ret = BT_COMPONENT_STATUS_ERROR;
-               goto error;
-       }
-
-       writer_component->single_trace = false;
-       ret = apply_one_bool("single-trace", params,
-                       &writer_component->single_trace, NULL);
-       if (ret != BT_COMPONENT_STATUS_OK) {
-               goto end;
-       }
-
-       ret = bt_self_component_set_user_data(component, writer_component);
-       if (ret != BT_COMPONENT_STATUS_OK) {
-               goto error;
-       }
-
-end:
-       return ret;
-error:
-       destroy_writer_component_data(writer_component);
-       g_free(writer_component);
-       return ret;
-}
diff --git a/plugins/ctf/fs-sink/writer.h b/plugins/ctf/fs-sink/writer.h
deleted file mode 100644 (file)
index 34f54ae..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-#ifndef BABELTRACE_PLUGIN_WRITER_H
-#define BABELTRACE_PLUGIN_WRITER_H
-
-/*
- * BabelTrace - CTF Writer Output Plug-in
- *
- * Copyright 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Author: Jérémie Galarneau <jeremie.galarneau@efficios.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdbool.h>
-#include <babeltrace/babeltrace-internal.h>
-#include <babeltrace/babeltrace.h>
-
-struct writer_component {
-       GString *base_path;
-       GString *trace_name_base;
-       /* For the directory name suffix. */
-       int trace_id;
-       /* Map between bt_trace and struct fs_writer. */
-       GHashTable *trace_map;
-       FILE *err;
-       bt_message_iterator *input_iterator;
-       bool error;
-       bool single_trace;
-       unsigned int nr_traces;
-};
-
-enum fs_writer_stream_state {
-       /*
-        * We know the stream exists but we have never received a
-        * stream_begin message for it.
-        */
-       FS_WRITER_UNKNOWN_STREAM,
-       /* We know this stream is active (between stream_begin and _end). */
-       FS_WRITER_ACTIVE_STREAM,
-       /* We have received a stream_end for this stream. */
-       FS_WRITER_COMPLETED_STREAM,
-};
-
-struct fs_writer {
-       struct bt_ctf_writer *writer;
-       const bt_trace *trace;
-       const bt_trace *writer_trace;
-       struct writer_component *writer_component;
-       int static_listener_id;
-       int trace_static;
-       /* Map between reader and writer stream. */
-       GHashTable *stream_map;
-       /* Map between reader and writer stream class. */
-       GHashTable *stream_class_map;
-       GHashTable *stream_states;
-};
-
-BT_HIDDEN
-void writer_close(struct writer_component *writer_component,
-               struct fs_writer *fs_writer);
-BT_HIDDEN
-bt_component_status writer_output_event(struct writer_component *writer,
-               const bt_event *event);
-BT_HIDDEN
-bt_component_status writer_new_packet(struct writer_component *writer,
-               const bt_packet *packet);
-BT_HIDDEN
-bt_component_status writer_close_packet(struct writer_component *writer,
-               const bt_packet *packet);
-BT_HIDDEN
-bt_component_status writer_stream_begin(struct writer_component *writer,
-               const bt_stream *stream);
-BT_HIDDEN
-bt_component_status writer_stream_end(struct writer_component *writer,
-               const bt_stream *stream);
-
-BT_HIDDEN
-bt_component_status writer_component_init(
-       bt_self_component *component, bt_value *params,
-       void *init_method_data);
-
-BT_HIDDEN
-bt_component_status writer_run(bt_self_component *component);
-
-BT_HIDDEN
-void writer_component_port_connected(
-               bt_self_component *component,
-               struct bt_private_port *self_port,
-               const bt_port *other_port);
-
-BT_HIDDEN
-void writer_component_finalize(bt_self_component *component);
-
-#endif /* BABELTRACE_PLUGIN_WRITER_H */
index 3f8ccf0a9ffb8e23c11f94077bfa2f5dc5b7bed4..352f76348df25f4abcfd361e5cf4059d83ed70b3 100644 (file)
@@ -1,8 +1,8 @@
 AM_CPPFLAGS += -I$(top_srcdir)/plugins
 
-noinst_LTLIBRARIES = libbabeltrace-plugin-ctf-fs.la
+noinst_LTLIBRARIES = libbabeltrace-plugin-ctf-fs-src.la
 
-libbabeltrace_plugin_ctf_fs_la_SOURCES = \
+libbabeltrace_plugin_ctf_fs_src_la_SOURCES = \
        data-stream-file.c \
        data-stream-file.h \
        file.c \
index 43afb7754b5843c53ac044af9301f2d4f55d8d94..1e99e1cd2a16d16ed51cbc2619884029be479338 100644 (file)
@@ -27,7 +27,9 @@
  */
 
 #include <babeltrace/babeltrace.h>
+
 #include "fs-src/fs.h"
+#include "fs-sink/fs-sink.h"
 
 #ifndef BT_BUILT_IN_PLUGINS
 BT_PLUGIN_MODULE();
@@ -53,15 +55,15 @@ BT_PLUGIN_SOURCE_COMPONENT_CLASS_MESSAGE_ITERATOR_FINALIZE_METHOD(fs,
 BT_PLUGIN_SOURCE_COMPONENT_CLASS_MESSAGE_ITERATOR_SEEK_BEGINNING_METHOD(fs,
        ctf_fs_iterator_seek_beginning);
 
-#if 0
 /* ctf.fs sink */
-BT_PLUGIN_SINK_COMPONENT_CLASS(fs, writer_run);
-BT_PLUGIN_SINK_COMPONENT_CLASS_INIT_METHOD(fs, writer_component_init);
-BT_PLUGIN_SINK_COMPONENT_CLASS_PORT_CONNECTED_METHOD(fs,
-               writer_component_port_connected);
-BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(fs, writer_component_finalize);
+BT_PLUGIN_SINK_COMPONENT_CLASS(fs, ctf_fs_sink_consume);
+BT_PLUGIN_SINK_COMPONENT_CLASS_INIT_METHOD(fs, ctf_fs_sink_init);
+BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(fs, ctf_fs_sink_finalize);
+BT_PLUGIN_SINK_COMPONENT_CLASS_GRAPH_IS_CONFIGURED_METHOD(fs,
+       ctf_fs_sink_graph_is_configured);
 BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(fs, "Write CTF traces to the file system.");
 
+#if 0
 /* ctf.lttng-live source */
 BT_PLUGIN_SOURCE_COMPONENT_CLASS_WITH_ID(auto, lttng_live, "lttng-live",
        lttng_live_iterator_next);
This page took 0.135733 seconds and 5 git commands to generate.