ctf.fs source: add trace file rotation (stream instance ID) support
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 24 May 2017 00:47:58 +0000 (20:47 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Sun, 28 May 2017 16:57:44 +0000 (12:57 -0400)
ctf.fs source is changed as such:

* A CTF FS trace owns zero or more stream file groups.

* A stream file group owns a unique CTF IR stream object and one or more
  stream file infos.

* A stream file info contains a path and a beginning timestamp (the
  beginning timestamp of the stream file's first packet).

* Stream file infos are ordered by time in a stream file group.

* One output port is created for each stream file group. It is also
  associated to this stream file group through private port's user data.

  The name of the port is always the stream file group's first stream
  file info's path, which is always the path of stream file containing
  the oldest packet. This can be useful to isolate a group of stream
  files by name and possibly CPU ID, e.g. (CPU 1 of LTTng channel
  `my-channel`):

      --connect='ctf.*my-channel_1_*:mux'

  or all CPUs of LTTng channel `hello-chan`:

      --connect='ctf.*hello-chan_*:mux'

* The user data of a notification iterator contains its upstream port's
  stream file group and a stream file info index.

* For a given notification iterator, one data stream file object (owner
  of a binary notification iterator and the one which does the stream
  file memory mapping) is always active. When it returns a
  BT_NOTIFICATION_ITERATOR_STATUS_END status, the data stream file is
  destroyed and the next stream file info is used to create a new data
  stream file, from which the real next notification is obtained. If
  there's no more stream file info in the stream file group, then
  BT_NOTIFICATION_ITERATOR_STATUS_END is returned.

This patch effectively makes a ctf.fs source component create one unique
stream, and thus one port, for each stream instance of each trace,
therefore supporting trace file rotation. Only one stream file is
opened and memory mapped at any time for a given stream instance.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
plugins/ctf/fs-src/Makefile.am
plugins/ctf/fs-src/data-stream-file.c [new file with mode: 0644]
plugins/ctf/fs-src/data-stream-file.h [new file with mode: 0644]
plugins/ctf/fs-src/data-stream.c [deleted file]
plugins/ctf/fs-src/data-stream.h [deleted file]
plugins/ctf/fs-src/fs.c
plugins/ctf/fs-src/fs.h

index 3c5c8b6fd147d11ac3e490587bcc67429a890b98..70be5f0f2392f0788c2d5906bb112ec8562bc015 100644 (file)
@@ -5,13 +5,13 @@ noinst_LTLIBRARIES = libbabeltrace-plugin-ctf-fs.la
 libbabeltrace_plugin_ctf_fs_la_LIBADD = \
        $(builddir)/../common/libbabeltrace-plugin-ctf-common.la
 libbabeltrace_plugin_ctf_fs_la_SOURCES = \
-       fs.c \
-       data-stream.c \
-       metadata.c \
+       data-stream-file.c \
+       data-stream-file.h \
        file.c \
-       data-stream.h \
        file.h \
+       fs.c \
        fs.h \
        lttng-index.h \
+       metadata.c \
        metadata.h \
        print.h
diff --git a/plugins/ctf/fs-src/data-stream-file.c b/plugins/ctf/fs-src/data-stream-file.c
new file mode 100644 (file)
index 0000000..bf3dda6
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2016-2017 - Philippe Proulx <pproulx@efficios.com>
+ * Copyright 2016 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright 2010-2011 - EfficiOS Inc. and Linux Foundation
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <babeltrace/ctf-ir/stream.h>
+#include <babeltrace/graph/notification-iterator.h>
+#include <babeltrace/graph/notification-stream.h>
+#include <babeltrace/graph/notification-event.h>
+#include <babeltrace/graph/notification-packet.h>
+#include "file.h"
+#include "metadata.h"
+#include "../common/notif-iter/notif-iter.h"
+#include <assert.h>
+#include "data-stream-file.h"
+
+#define PRINT_ERR_STREAM       ctf_fs->error_fp
+#define PRINT_PREFIX           "ctf-fs-data-stream"
+#define PRINT_DBG_CHECK                ctf_fs_debug
+#include "../print.h"
+
+static inline
+size_t remaining_mmap_bytes(struct ctf_fs_ds_file *ds_file)
+{
+       return ds_file->mmap_valid_len - ds_file->request_offset;
+}
+
+static
+int ds_file_munmap(struct ctf_fs_ds_file *ds_file)
+{
+       int ret = 0;
+       struct ctf_fs_component *ctf_fs = ds_file->file->ctf_fs;
+
+       if (!ds_file->mmap_addr) {
+               goto end;
+       }
+
+       if (munmap(ds_file->mmap_addr, ds_file->mmap_len)) {
+               PERR("Cannot memory-unmap address %p (size %zu) of file \"%s\" (%p): %s\n",
+                       ds_file->mmap_addr, ds_file->mmap_len,
+                       ds_file->file->path->str, ds_file->file->fp,
+                       strerror(errno));
+               ret = -1;
+               goto end;
+       }
+
+       ds_file->mmap_addr = NULL;
+
+end:
+       return ret;
+}
+
+static
+enum bt_ctf_notif_iter_medium_status ds_file_mmap_next(
+               struct ctf_fs_ds_file *ds_file)
+{
+       enum bt_ctf_notif_iter_medium_status ret =
+                       BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK;
+       struct ctf_fs_component *ctf_fs = ds_file->file->ctf_fs;
+
+       /* Unmap old region */
+       if (ds_file->mmap_addr) {
+               if (ds_file_munmap(ds_file)) {
+                       goto error;
+               }
+
+               ds_file->mmap_offset += ds_file->mmap_valid_len;
+               ds_file->request_offset = 0;
+       }
+
+       ds_file->mmap_valid_len = MIN(ds_file->file->size - ds_file->mmap_offset,
+                       ds_file->mmap_max_len);
+       if (ds_file->mmap_valid_len == 0) {
+               ret = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF;
+               goto end;
+       }
+       /* Round up to next page, assuming page size being a power of 2. */
+       ds_file->mmap_len = (ds_file->mmap_valid_len + ctf_fs->page_size - 1)
+                       & ~(ctf_fs->page_size - 1);
+       /* Map new region */
+       assert(ds_file->mmap_len);
+       ds_file->mmap_addr = mmap((void *) 0, ds_file->mmap_len,
+                       PROT_READ, MAP_PRIVATE, fileno(ds_file->file->fp),
+                       ds_file->mmap_offset);
+       if (ds_file->mmap_addr == MAP_FAILED) {
+               PERR("Cannot memory-map address (size %zu) of file \"%s\" (%p) at offset %zu: %s\n",
+                               ds_file->mmap_len, ds_file->file->path->str,
+                               ds_file->file->fp, ds_file->mmap_offset,
+                               strerror(errno));
+               goto error;
+       }
+
+       goto end;
+error:
+       ds_file_munmap(ds_file);
+       ret = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_ERROR;
+end:
+       return ret;
+}
+
+static
+enum bt_ctf_notif_iter_medium_status medop_request_bytes(
+               size_t request_sz, uint8_t **buffer_addr,
+               size_t *buffer_sz, void *data)
+{
+       enum bt_ctf_notif_iter_medium_status status =
+               BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK;
+       struct ctf_fs_ds_file *ds_file = data;
+       struct ctf_fs_component *ctf_fs = ds_file->file->ctf_fs;
+
+       if (request_sz == 0) {
+               goto end;
+       }
+
+       /* Check if we have at least one memory-mapped byte left */
+       if (remaining_mmap_bytes(ds_file) == 0) {
+               /* Are we at the end of the file? */
+               if (ds_file->mmap_offset >= ds_file->file->size) {
+                       PDBG("Reached end of file \"%s\" (%p)\n",
+                               ds_file->file->path->str, ds_file->file->fp);
+                       status = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF;
+                       goto end;
+               }
+
+               status = ds_file_mmap_next(ds_file);
+               switch (status) {
+               case BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK:
+                       break;
+               case BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF:
+                       goto end;
+               default:
+                       PERR("Cannot memory-map next region of file \"%s\" (%p)\n",
+                                       ds_file->file->path->str,
+                                       ds_file->file->fp);
+                       goto error;
+               }
+       }
+
+       *buffer_sz = MIN(remaining_mmap_bytes(ds_file), request_sz);
+       *buffer_addr = ((uint8_t *) ds_file->mmap_addr) + ds_file->request_offset;
+       ds_file->request_offset += *buffer_sz;
+       goto end;
+
+error:
+       status = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_ERROR;
+
+end:
+       return status;
+}
+
+static
+struct bt_ctf_stream *medop_get_stream(
+               struct bt_ctf_stream_class *stream_class, void *data)
+{
+       struct ctf_fs_ds_file *ds_file = data;
+       struct bt_ctf_stream_class *ds_file_stream_class;
+       struct bt_ctf_stream *stream = NULL;
+
+       ds_file_stream_class = bt_ctf_stream_get_class(ds_file->stream);
+       bt_put(ds_file_stream_class);
+
+       if (stream_class != ds_file_stream_class) {
+               /*
+                * Not supported: two packets described by two different
+                * stream classes within the same data stream file.
+                */
+               goto end;
+       }
+
+       stream = ds_file->stream;
+
+end:
+       return stream;
+}
+
+static struct bt_ctf_notif_iter_medium_ops medops = {
+       .request_bytes = medop_request_bytes,
+       .get_stream = medop_get_stream,
+};
+
+static
+int build_index_from_idx_file(struct ctf_fs_ds_file *ds_file)
+{
+       int ret = 0;
+       gchar *directory = NULL;
+       gchar *basename = NULL;
+       GString *index_basename = NULL;
+       gchar *index_file_path = NULL;
+       GMappedFile *mapped_file = NULL;
+       gsize filesize;
+       const struct ctf_packet_index_file_hdr *header;
+       const char *mmap_begin, *file_pos;
+       struct index_entry *index;
+       uint64_t total_packets_size = 0;
+       size_t file_index_entry_size;
+       size_t file_entry_count;
+       size_t i;
+
+       /* Look for index file in relative path index/name.idx. */
+       basename = g_path_get_basename(ds_file->file->path->str);
+       if (!basename) {
+               ret = -1;
+               goto end;
+       }
+
+       directory = g_path_get_dirname(ds_file->file->path->str);
+       if (!directory) {
+               ret = -1;
+               goto end;
+       }
+
+       index_basename = g_string_new(basename);
+       if (!index_basename) {
+               ret = -1;
+               goto end;
+       }
+
+       g_string_append(index_basename, ".idx");
+       index_file_path = g_build_filename(directory, "index",
+                       index_basename->str, NULL);
+       mapped_file = g_mapped_file_new(index_file_path, FALSE, NULL);
+       if (!mapped_file) {
+               ret = -1;
+               goto end;
+       }
+       filesize = g_mapped_file_get_length(mapped_file);
+       if (filesize < sizeof(*header)) {
+               printf_error("Invalid LTTng trace index: file size < header size");
+               ret = -1;
+               goto end;
+       }
+
+       mmap_begin = g_mapped_file_get_contents(mapped_file);
+       header = (struct ctf_packet_index_file_hdr *) mmap_begin;
+
+       file_pos = g_mapped_file_get_contents(mapped_file) + sizeof(*header);
+       if (be32toh(header->magic) != CTF_INDEX_MAGIC) {
+               printf_error("Invalid LTTng trace index: \"magic\" validation failed");
+               ret = -1;
+               goto end;
+       }
+
+       file_index_entry_size = be32toh(header->packet_index_len);
+       file_entry_count = (filesize - sizeof(*header)) / file_index_entry_size;
+       if ((filesize - sizeof(*header)) % (file_entry_count * file_index_entry_size)) {
+               printf_error("Invalid index file size; not a multiple of index entry size");
+               ret = -1;
+               goto end;
+       }
+
+       ds_file->index.entries = g_array_sized_new(FALSE, TRUE,
+                       sizeof(struct index_entry), file_entry_count);
+       if (!ds_file->index.entries) {
+               ret = -1;
+               goto end;
+       }
+       index = (struct index_entry *) ds_file->index.entries->data;
+       for (i = 0; i < file_entry_count; i++) {
+               struct ctf_packet_index *file_index =
+                               (struct ctf_packet_index *) file_pos;
+               uint64_t packet_size = be64toh(file_index->packet_size);
+
+               if (packet_size % CHAR_BIT) {
+                       ret = -1;
+                       printf_error("Invalid packet size encountered in index file");
+                       goto invalid_index;
+               }
+
+               /* Convert size in bits to bytes. */
+               packet_size /= CHAR_BIT;
+               index->packet_size = packet_size;
+
+               index->offset = be64toh(file_index->offset);
+               if (i != 0 && index->offset < (index - 1)->offset) {
+                       printf_error("Invalid, non-monotonic, packet offset encountered in index file");
+                       ret = -1;
+                       goto invalid_index;
+               }
+
+               index->timestamp_begin = be64toh(file_index->timestamp_begin);
+               index->timestamp_end = be64toh(file_index->timestamp_end);
+               if (index->timestamp_end < index->timestamp_begin) {
+                       printf_error("Invalid packet time bounds encountered in index file");
+                       ret = -1;
+                       goto invalid_index;
+               }
+
+               total_packets_size += packet_size;
+               file_pos += file_index_entry_size;
+               index++;
+       }
+
+       /* Validate that the index addresses the complete stream. */
+       if (ds_file->file->size != total_packets_size) {
+               printf_error("Invalid index; indexed size != stream file size");
+               ret = -1;
+               goto invalid_index;
+       }
+end:
+       g_free(directory);
+       g_free(basename);
+       g_free(index_file_path);
+       if (index_basename) {
+               g_string_free(index_basename, TRUE);
+       }
+       if (mapped_file) {
+               g_mapped_file_unref(mapped_file);
+       }
+       return ret;
+invalid_index:
+       g_array_free(ds_file->index.entries, TRUE);
+       goto end;
+}
+
+static
+int build_index_from_data_stream_file(struct ctf_fs_ds_file *stream)
+{
+       return 0;
+}
+
+static
+int init_stream_index(struct ctf_fs_ds_file *ds_file)
+{
+       int ret;
+
+       ret = build_index_from_idx_file(ds_file);
+       if (!ret) {
+               goto end;
+       }
+
+       ret = build_index_from_data_stream_file(ds_file);
+end:
+       return ret;
+}
+
+BT_HIDDEN
+struct ctf_fs_ds_file *ctf_fs_ds_file_create(
+               struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_stream *stream, const char *path,
+               bool build_index)
+{
+       int ret;
+       struct ctf_fs_ds_file *ds_file = g_new0(struct ctf_fs_ds_file, 1);
+
+       if (!ds_file) {
+               goto error;
+       }
+
+       ds_file->file = ctf_fs_file_create(ctf_fs_trace->ctf_fs);
+       if (!ds_file->file) {
+               goto error;
+       }
+
+       ds_file->stream = bt_get(stream);
+       ds_file->cc_prio_map = bt_get(ctf_fs_trace->cc_prio_map);
+       g_string_assign(ds_file->file->path, path);
+       ret = ctf_fs_file_open(ctf_fs_trace->ctf_fs, ds_file->file, "rb");
+       if (ret) {
+               goto error;
+       }
+
+       ds_file->notif_iter = bt_ctf_notif_iter_create(
+               ctf_fs_trace->metadata->trace,
+               ctf_fs_trace->ctf_fs->page_size, medops, ds_file,
+               ctf_fs_trace->ctf_fs->error_fp);
+       if (!ds_file->notif_iter) {
+               goto error;
+       }
+
+       ds_file->mmap_max_len = ctf_fs_trace->ctf_fs->page_size * 2048;
+
+       if (build_index) {
+               ret = init_stream_index(ds_file);
+               if (ret) {
+                       goto error;
+               }
+       }
+
+       goto end;
+
+error:
+       /* Do not touch "borrowed" file. */
+       ctf_fs_ds_file_destroy(ds_file);
+       ds_file = NULL;
+
+end:
+       return ds_file;
+}
+
+BT_HIDDEN
+void ctf_fs_ds_file_destroy(struct ctf_fs_ds_file *ds_file)
+{
+       if (!ds_file) {
+               return;
+       }
+
+       bt_put(ds_file->cc_prio_map);
+       bt_put(ds_file->stream);
+       (void) ds_file_munmap(ds_file);
+
+       if (ds_file->file) {
+               ctf_fs_file_destroy(ds_file->file);
+       }
+
+       if (ds_file->notif_iter) {
+               bt_ctf_notif_iter_destroy(ds_file->notif_iter);
+       }
+
+       if (ds_file->index.entries) {
+               g_array_free(ds_file->index.entries, TRUE);
+       }
+
+       g_free(ds_file);
+}
+
+BT_HIDDEN
+struct bt_notification_iterator_next_return ctf_fs_ds_file_next(
+               struct ctf_fs_ds_file *ds_file)
+{
+       enum bt_ctf_notif_iter_status notif_iter_status;
+       struct bt_notification_iterator_next_return ret = {
+               .status = BT_NOTIFICATION_ITERATOR_STATUS_ERROR,
+               .notification = NULL,
+       };
+
+       notif_iter_status = bt_ctf_notif_iter_get_next_notification(
+               ds_file->notif_iter, ds_file->cc_prio_map, &ret.notification);
+
+       switch (notif_iter_status) {
+       case BT_CTF_NOTIF_ITER_STATUS_EOF:
+               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_END;
+               break;
+       case BT_CTF_NOTIF_ITER_STATUS_OK:
+               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_OK;
+               break;
+       case BT_CTF_NOTIF_ITER_STATUS_AGAIN:
+               /*
+                * Should not make it this far as this is
+                * medium-specific; there is nothing for the user to do
+                * and it should have been handled upstream.
+                */
+               assert(false);
+       case BT_CTF_NOTIF_ITER_STATUS_INVAL:
+       case BT_CTF_NOTIF_ITER_STATUS_ERROR:
+       default:
+               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_ERROR;
+               break;
+       }
+
+       return ret;
+}
+
+BT_HIDDEN
+int ctf_fs_ds_file_get_packet_header_context_fields(
+               struct ctf_fs_trace *ctf_fs_trace, const char *path,
+               struct bt_ctf_field **packet_header_field,
+               struct bt_ctf_field **packet_context_field)
+{
+       enum bt_ctf_notif_iter_status notif_iter_status;
+       struct ctf_fs_ds_file *ds_file;
+       int ret = 0;
+
+       ds_file = ctf_fs_ds_file_create(ctf_fs_trace, NULL, path, false);
+       if (!ds_file) {
+               goto error;
+       }
+
+       notif_iter_status = bt_ctf_notif_iter_get_packet_header_context_fields(
+               ds_file->notif_iter, packet_header_field, packet_context_field);
+       switch (notif_iter_status) {
+       case BT_CTF_NOTIF_ITER_STATUS_EOF:
+       case BT_CTF_NOTIF_ITER_STATUS_OK:
+               break;
+       case BT_CTF_NOTIF_ITER_STATUS_AGAIN:
+               assert(false);
+       case BT_CTF_NOTIF_ITER_STATUS_INVAL:
+       case BT_CTF_NOTIF_ITER_STATUS_ERROR:
+       default:
+               goto error;
+               break;
+       }
+
+       goto end;
+
+error:
+       ret = -1;
+
+       if (packet_header_field) {
+               bt_put(*packet_header_field);
+       }
+
+       if (packet_context_field) {
+               bt_put(*packet_context_field);
+       }
+
+end:
+       ctf_fs_ds_file_destroy(ds_file);
+       return ret;
+}
diff --git a/plugins/ctf/fs-src/data-stream-file.h b/plugins/ctf/fs-src/data-stream-file.h
new file mode 100644 (file)
index 0000000..e5bc7c7
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef CTF_FS_DS_FILE_H
+#define CTF_FS_DS_FILE_H
+
+/*
+ * Copyright 2016 - 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 <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <babeltrace/babeltrace-internal.h>
+#include <babeltrace/ctf-ir/trace.h>
+
+#include "../common/notif-iter/notif-iter.h"
+#include "lttng-index.h"
+
+struct ctf_fs_component;
+struct ctf_fs_file;
+struct ctf_fs_trace;
+struct ctf_fs_ds_file;
+
+struct index_entry {
+       uint64_t offset; /* in bytes. */
+       uint64_t packet_size; /* in bytes. */
+       /* relative to the packet context field's mapped clock. */
+       uint64_t timestamp_begin, timestamp_end;
+};
+
+struct index {
+       GArray *entries; /* Array of struct index_entry. */
+};
+
+BT_HIDDEN
+struct ctf_fs_ds_file *ctf_fs_ds_file_create(
+               struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_stream *stream, const char *path,
+               bool build_index);
+
+BT_HIDDEN
+int ctf_fs_ds_file_get_packet_header_context_fields(
+               struct ctf_fs_trace *ctf_fs_trace, const char *path,
+               struct bt_ctf_field **packet_header_field,
+               struct bt_ctf_field **packet_context_field);
+
+BT_HIDDEN
+void ctf_fs_ds_file_destroy(struct ctf_fs_ds_file *stream);
+
+BT_HIDDEN
+struct bt_notification_iterator_next_return ctf_fs_ds_file_next(
+               struct ctf_fs_ds_file *stream);
+
+#endif /* CTF_FS_DS_FILE_H */
diff --git a/plugins/ctf/fs-src/data-stream.c b/plugins/ctf/fs-src/data-stream.c
deleted file mode 100644 (file)
index 851c094..0000000
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright 2016 - Philippe Proulx <pproulx@efficios.com>
- * Copyright 2016 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
- * Copyright 2010-2011 - EfficiOS Inc. and Linux Foundation
- *
- * 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 <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <glib.h>
-#include <inttypes.h>
-#include <sys/mman.h>
-#include <babeltrace/ctf-ir/stream.h>
-#include <babeltrace/graph/notification-iterator.h>
-#include <babeltrace/graph/notification-stream.h>
-#include <babeltrace/graph/notification-event.h>
-#include <babeltrace/graph/notification-packet.h>
-#include "file.h"
-#include "metadata.h"
-#include "../common/notif-iter/notif-iter.h"
-#include <assert.h>
-#include "data-stream.h"
-
-#define PRINT_ERR_STREAM       ctf_fs->error_fp
-#define PRINT_PREFIX           "ctf-fs-data-stream"
-#define PRINT_DBG_CHECK                ctf_fs_debug
-#include "../print.h"
-
-static inline
-size_t remaining_mmap_bytes(struct ctf_fs_stream *stream)
-{
-       return stream->mmap_valid_len - stream->request_offset;
-}
-
-static
-int stream_munmap(struct ctf_fs_stream *stream)
-{
-       int ret = 0;
-       struct ctf_fs_component *ctf_fs = stream->file->ctf_fs;
-
-       if (munmap(stream->mmap_addr, stream->mmap_len)) {
-               PERR("Cannot memory-unmap address %p (size %zu) of file \"%s\" (%p): %s\n",
-                       stream->mmap_addr, stream->mmap_len,
-                       stream->file->path->str, stream->file->fp,
-                       strerror(errno));
-               ret = -1;
-               goto end;
-       }
-end:
-       return ret;
-}
-
-static
-enum bt_ctf_notif_iter_medium_status mmap_next(struct ctf_fs_stream *stream)
-{
-       enum bt_ctf_notif_iter_medium_status ret =
-                       BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK;
-       struct ctf_fs_component *ctf_fs = stream->file->ctf_fs;
-
-       /* Unmap old region */
-       if (stream->mmap_addr) {
-               if (stream_munmap(stream)) {
-                       goto error;
-               }
-
-               stream->mmap_offset += stream->mmap_valid_len;
-               stream->request_offset = 0;
-       }
-
-       stream->mmap_valid_len = MIN(stream->file->size - stream->mmap_offset,
-                       stream->mmap_max_len);
-       if (stream->mmap_valid_len == 0) {
-               ret = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF;
-               goto end;
-       }
-       /* Round up to next page, assuming page size being a power of 2. */
-       stream->mmap_len = (stream->mmap_valid_len + ctf_fs->page_size - 1)
-                       & ~(ctf_fs->page_size - 1);
-       /* Map new region */
-       assert(stream->mmap_len);
-       stream->mmap_addr = mmap((void *) 0, stream->mmap_len,
-                       PROT_READ, MAP_PRIVATE, fileno(stream->file->fp),
-                       stream->mmap_offset);
-       if (stream->mmap_addr == MAP_FAILED) {
-               PERR("Cannot memory-map address (size %zu) of file \"%s\" (%p) at offset %zu: %s\n",
-                               stream->mmap_len, stream->file->path->str,
-                               stream->file->fp, stream->mmap_offset,
-                               strerror(errno));
-               goto error;
-       }
-
-       goto end;
-error:
-       stream_munmap(stream);
-       ret = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_ERROR;
-end:
-       return ret;
-}
-
-static
-enum bt_ctf_notif_iter_medium_status medop_request_bytes(
-               size_t request_sz, uint8_t **buffer_addr,
-               size_t *buffer_sz, void *data)
-{
-       enum bt_ctf_notif_iter_medium_status status =
-               BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK;
-       struct ctf_fs_stream *stream = data;
-       struct ctf_fs_component *ctf_fs = stream->file->ctf_fs;
-
-       if (request_sz == 0) {
-               goto end;
-       }
-
-       /* Check if we have at least one memory-mapped byte left */
-       if (remaining_mmap_bytes(stream) == 0) {
-               /* Are we at the end of the file? */
-               if (stream->mmap_offset >= stream->file->size) {
-                       PDBG("Reached end of file \"%s\" (%p)\n",
-                               stream->file->path->str, stream->file->fp);
-                       status = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF;
-                       goto end;
-               }
-
-               status = mmap_next(stream);
-               switch (status) {
-               case BT_CTF_NOTIF_ITER_MEDIUM_STATUS_OK:
-                       break;
-               case BT_CTF_NOTIF_ITER_MEDIUM_STATUS_EOF:
-                       goto end;
-               default:
-                       PERR("Cannot memory-map next region of file \"%s\" (%p)\n",
-                                       stream->file->path->str,
-                                       stream->file->fp);
-                       goto error;
-               }
-       }
-
-       *buffer_sz = MIN(remaining_mmap_bytes(stream), request_sz);
-       *buffer_addr = ((uint8_t *) stream->mmap_addr) + stream->request_offset;
-       stream->request_offset += *buffer_sz;
-       goto end;
-
-error:
-       status = BT_CTF_NOTIF_ITER_MEDIUM_STATUS_ERROR;
-
-end:
-       return status;
-}
-
-static
-struct bt_ctf_stream *medop_get_stream(
-               struct bt_ctf_stream_class *stream_class, void *data)
-{
-       struct ctf_fs_stream *fs_stream = data;
-       struct ctf_fs_component *ctf_fs = fs_stream->file->ctf_fs;
-
-       if (!fs_stream->stream) {
-               int64_t id = bt_ctf_stream_class_get_id(stream_class);
-
-               PDBG("Creating stream out of stream class %" PRId64 "\n", id);
-               fs_stream->stream = bt_ctf_stream_create(stream_class,
-                               fs_stream->file->path->str);
-               if (!fs_stream->stream) {
-                       PERR("Cannot create stream (stream class %" PRId64 ")\n",
-                                       id);
-               }
-       }
-
-       return fs_stream->stream;
-}
-
-static struct bt_ctf_notif_iter_medium_ops medops = {
-       .request_bytes = medop_request_bytes,
-       .get_stream = medop_get_stream,
-};
-
-static
-int build_index_from_idx_file(struct ctf_fs_stream *stream)
-{
-       int ret = 0;
-       gchar *directory = NULL;
-       gchar *basename = NULL;
-       GString *index_basename = NULL;
-       gchar *index_file_path = NULL;
-       GMappedFile *mapped_file = NULL;
-       gsize filesize;
-       const struct ctf_packet_index_file_hdr *header;
-       const char *mmap_begin, *file_pos;
-       struct index_entry *index;
-       uint64_t total_packets_size = 0;
-       size_t file_index_entry_size;
-       size_t file_entry_count;
-       size_t i;
-
-       /* Look for index file in relative path index/name.idx. */
-       basename = g_path_get_basename(stream->file->path->str);
-       if (!basename) {
-               ret = -1;
-               goto end;
-       }
-
-       directory = g_path_get_dirname(stream->file->path->str);
-       if (!directory) {
-               ret = -1;
-               goto end;
-       }
-
-       index_basename = g_string_new(basename);
-       if (!index_basename) {
-               ret = -1;
-               goto end;
-       }
-
-       g_string_append(index_basename, ".idx");
-       index_file_path = g_build_filename(directory, "index",
-                       index_basename->str, NULL);
-       mapped_file = g_mapped_file_new(index_file_path, FALSE, NULL);
-       if (!mapped_file) {
-               ret = -1;
-               goto end;
-       }
-       filesize = g_mapped_file_get_length(mapped_file);
-       if (filesize < sizeof(*header)) {
-               printf_error("Invalid LTTng trace index: file size < header size");
-               ret = -1;
-               goto end;
-       }
-
-       mmap_begin = g_mapped_file_get_contents(mapped_file);
-       header = (struct ctf_packet_index_file_hdr *) mmap_begin;
-
-       file_pos = g_mapped_file_get_contents(mapped_file) + sizeof(*header);
-       if (be32toh(header->magic) != CTF_INDEX_MAGIC) {
-               printf_error("Invalid LTTng trace index: \"magic\" validation failed");
-               ret = -1;
-               goto end;
-       }
-
-       file_index_entry_size = be32toh(header->packet_index_len);
-       file_entry_count = (filesize - sizeof(*header)) / file_index_entry_size;
-       if ((filesize - sizeof(*header)) % (file_entry_count * file_index_entry_size)) {
-               printf_error("Invalid index file size; not a multiple of index entry size");
-               ret = -1;
-               goto end;
-       }
-
-       stream->index.entries = g_array_sized_new(FALSE, TRUE,
-                       sizeof(struct index_entry), file_entry_count);
-       if (!stream->index.entries) {
-               ret = -1;
-               goto end;
-       }
-       index = (struct index_entry *) stream->index.entries->data;
-       for (i = 0; i < file_entry_count; i++) {
-               struct ctf_packet_index *file_index =
-                               (struct ctf_packet_index *) file_pos;
-               uint64_t packet_size = be64toh(file_index->packet_size);
-
-               if (packet_size % CHAR_BIT) {
-                       ret = -1;
-                       printf_error("Invalid packet size encountered in index file");
-                       goto invalid_index;
-               }
-
-               /* Convert size in bits to bytes. */
-               packet_size /= CHAR_BIT;
-               index->packet_size = packet_size;
-
-               index->offset = be64toh(file_index->offset);
-               if (i != 0 && index->offset < (index - 1)->offset) {
-                       printf_error("Invalid, non-monotonic, packet offset encountered in index file");
-                       ret = -1;
-                       goto invalid_index;
-               }
-
-               index->timestamp_begin = be64toh(file_index->timestamp_begin);
-               index->timestamp_end = be64toh(file_index->timestamp_end);
-               if (index->timestamp_end < index->timestamp_begin) {
-                       printf_error("Invalid packet time bounds encountered in index file");
-                       ret = -1;
-                       goto invalid_index;
-               }
-
-               total_packets_size += packet_size;
-               file_pos += file_index_entry_size;
-               index++;
-       }
-
-       /* Validate that the index addresses the complete stream. */
-       if (stream->file->size != total_packets_size) {
-               printf_error("Invalid index; indexed size != stream file size");
-               ret = -1;
-               goto invalid_index;
-       }
-end:
-       g_free(directory);
-       g_free(basename);
-       g_free(index_file_path);
-       if (index_basename) {
-               g_string_free(index_basename, TRUE);
-       }
-       if (mapped_file) {
-               g_mapped_file_unref(mapped_file);
-       }
-       return ret;
-invalid_index:
-       g_array_free(stream->index.entries, TRUE);
-       goto end;
-}
-
-static
-int build_index_from_stream(struct ctf_fs_stream *stream)
-{
-       return 0;
-}
-
-static
-int init_stream_index(struct ctf_fs_stream *stream)
-{
-       int ret;
-
-       ret = build_index_from_idx_file(stream);
-       if (!ret) {
-               goto end;
-       }
-
-       ret = build_index_from_stream(stream);
-end:
-       return ret;
-}
-
-BT_HIDDEN
-struct ctf_fs_stream *ctf_fs_stream_create(
-               struct ctf_fs_trace *ctf_fs_trace, const char *path)
-{
-       int ret;
-       struct ctf_fs_stream *ctf_fs_stream = g_new0(struct ctf_fs_stream, 1);
-
-       if (!ctf_fs_stream) {
-               goto error;
-       }
-
-       ctf_fs_stream->file = ctf_fs_file_create(ctf_fs_trace->ctf_fs);
-       if (!ctf_fs_stream->file) {
-               goto error;
-       }
-
-       ctf_fs_stream->cc_prio_map = bt_get(ctf_fs_trace->cc_prio_map);
-       g_string_assign(ctf_fs_stream->file->path, path);
-       ret = ctf_fs_file_open(ctf_fs_trace->ctf_fs, ctf_fs_stream->file, "rb");
-       if (ret) {
-               goto error;
-       }
-
-       ctf_fs_stream->notif_iter = bt_ctf_notif_iter_create(
-               ctf_fs_trace->metadata->trace,
-               ctf_fs_trace->ctf_fs->page_size, medops, ctf_fs_stream,
-               ctf_fs_trace->ctf_fs->error_fp);
-       if (!ctf_fs_stream->notif_iter) {
-               goto error;
-       }
-
-       ctf_fs_stream->mmap_max_len = ctf_fs_trace->ctf_fs->page_size * 2048;
-       ret = init_stream_index(ctf_fs_stream);
-       if (ret) {
-               goto error;
-       }
-
-       goto end;
-
-error:
-       /* Do not touch "borrowed" file. */
-       ctf_fs_stream_destroy(ctf_fs_stream);
-       ctf_fs_stream = NULL;
-
-end:
-       return ctf_fs_stream;
-}
-
-BT_HIDDEN
-void ctf_fs_stream_destroy(struct ctf_fs_stream *ctf_fs_stream)
-{
-       if (!ctf_fs_stream) {
-               return;
-       }
-
-       bt_put(ctf_fs_stream->cc_prio_map);
-
-       if (ctf_fs_stream->file) {
-               ctf_fs_file_destroy(ctf_fs_stream->file);
-       }
-
-       if (ctf_fs_stream->stream) {
-               BT_PUT(ctf_fs_stream->stream);
-       }
-
-       if (ctf_fs_stream->notif_iter) {
-               bt_ctf_notif_iter_destroy(ctf_fs_stream->notif_iter);
-       }
-
-       if (ctf_fs_stream->index.entries) {
-               g_array_free(ctf_fs_stream->index.entries, TRUE);
-       }
-
-       g_free(ctf_fs_stream);
-}
-
-BT_HIDDEN
-struct bt_notification_iterator_next_return ctf_fs_stream_next(
-               struct ctf_fs_stream *stream)
-{
-       enum bt_ctf_notif_iter_status notif_iter_status;
-       struct bt_notification_iterator_next_return ret = {
-               .status = BT_NOTIFICATION_ITERATOR_STATUS_ERROR,
-               .notification = NULL,
-       };
-
-       if (stream->end_reached) {
-               notif_iter_status = BT_CTF_NOTIF_ITER_STATUS_EOF;
-               goto translate_status;
-       }
-
-       notif_iter_status = bt_ctf_notif_iter_get_next_notification(
-               stream->notif_iter, stream->cc_prio_map, &ret.notification);
-       if (notif_iter_status != BT_CTF_NOTIF_ITER_STATUS_OK &&
-                       notif_iter_status != BT_CTF_NOTIF_ITER_STATUS_EOF) {
-               goto translate_status;
-       }
-
-       /* Should be handled in bt_ctf_notif_iter_get_next_notification. */
-       if (notif_iter_status == BT_CTF_NOTIF_ITER_STATUS_EOF) {
-               ret.notification = bt_notification_stream_end_create(
-                       stream->stream);
-               if (!ret.notification) {
-                       notif_iter_status = BT_CTF_NOTIF_ITER_STATUS_ERROR;
-                       goto translate_status;
-               }
-
-               notif_iter_status = BT_CTF_NOTIF_ITER_STATUS_OK;
-               stream->end_reached = true;
-       }
-
-translate_status:
-       switch (notif_iter_status) {
-       case BT_CTF_NOTIF_ITER_STATUS_EOF:
-               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_END;
-               break;
-       case BT_CTF_NOTIF_ITER_STATUS_OK:
-               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_OK;
-               break;
-       case BT_CTF_NOTIF_ITER_STATUS_AGAIN:
-               /*
-                * Should not make it this far as this is
-                * medium-specific; there is nothing for the user to do
-                * and it should have been handled upstream.
-                */
-               assert(false);
-       case BT_CTF_NOTIF_ITER_STATUS_INVAL:
-       case BT_CTF_NOTIF_ITER_STATUS_ERROR:
-       default:
-               ret.status = BT_NOTIFICATION_ITERATOR_STATUS_ERROR;
-               break;
-       }
-
-       return ret;
-}
diff --git a/plugins/ctf/fs-src/data-stream.h b/plugins/ctf/fs-src/data-stream.h
deleted file mode 100644 (file)
index 60a3110..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef CTF_FS_DATA_STREAM_H
-#define CTF_FS_DATA_STREAM_H
-
-/*
- * Copyright 2016 - 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 <stdio.h>
-#include <glib.h>
-#include <babeltrace/babeltrace-internal.h>
-#include <babeltrace/ctf-ir/trace.h>
-
-#include "../common/notif-iter/notif-iter.h"
-#include "lttng-index.h"
-
-struct ctf_fs_component;
-struct ctf_fs_file;
-struct ctf_fs_trace;
-struct ctf_fs_stream;
-
-struct index_entry {
-       uint64_t offset; /* in bytes. */
-       uint64_t packet_size; /* in bytes. */
-       /* relative to the packet context field's mapped clock. */
-       uint64_t timestamp_begin, timestamp_end;
-};
-
-struct index {
-       GArray *entries; /* Array of struct index_entry. */
-};
-
-BT_HIDDEN
-struct ctf_fs_stream *ctf_fs_stream_create(
-               struct ctf_fs_trace *ctf_fs_trace, const char *path);
-
-BT_HIDDEN
-void ctf_fs_stream_destroy(struct ctf_fs_stream *stream);
-
-BT_HIDDEN
-int ctf_fs_data_stream_open_streams(struct ctf_fs_component *ctf_fs);
-
-BT_HIDDEN
-struct bt_notification_iterator_next_return ctf_fs_stream_next(
-               struct ctf_fs_stream *stream);
-
-#endif /* CTF_FS_DATA_STREAM_H */
index 57468fbf1a7af44d319d6f8ae545ed022e52e449..965f5963e6f8f1ed15a0619be9bc8d508400a9dc 100644 (file)
@@ -27,6 +27,8 @@
 
 #include <babeltrace/ctf-ir/packet.h>
 #include <babeltrace/ctf-ir/clock-class.h>
+#include <babeltrace/ctf-ir/stream.h>
+#include <babeltrace/ctf-ir/fields.h>
 #include <babeltrace/graph/private-port.h>
 #include <babeltrace/graph/private-component.h>
 #include <babeltrace/graph/private-component-source.h>
 #include <plugins-common.h>
 #include <glib.h>
 #include <assert.h>
+#include <inttypes.h>
 #include <stdbool.h>
 #include <unistd.h>
 #include "fs.h"
 #include "metadata.h"
-#include "data-stream.h"
+#include "data-stream-file.h"
 #include "file.h"
 #include "../common/metadata/decoder.h"
 
 BT_HIDDEN
 bool ctf_fs_debug;
 
+static
+int notif_iter_data_set_current_ds_file(struct ctf_fs_notif_iter_data *notif_iter_data)
+{
+       struct ctf_fs_ds_file_info *ds_file_info;
+       int ret = 0;
+
+       assert(notif_iter_data->ds_file_info_index <
+               notif_iter_data->ds_file_group->ds_file_infos->len);
+       ds_file_info = g_ptr_array_index(
+               notif_iter_data->ds_file_group->ds_file_infos,
+               notif_iter_data->ds_file_info_index);
+
+       ctf_fs_ds_file_destroy(notif_iter_data->ds_file);
+       notif_iter_data->ds_file = ctf_fs_ds_file_create(
+               notif_iter_data->ds_file_group->ctf_fs_trace,
+               notif_iter_data->ds_file_group->stream,
+               ds_file_info->path->str, true);
+       if (!notif_iter_data->ds_file) {
+               ret = -1;
+       }
+
+       return ret;
+}
+
+static
+void ctf_fs_notif_iter_data_destroy(
+               struct ctf_fs_notif_iter_data *notif_iter_data)
+{
+       if (!notif_iter_data) {
+               return;
+       }
+
+       ctf_fs_ds_file_destroy(notif_iter_data->ds_file);
+       g_free(notif_iter_data);
+}
+
 struct bt_notification_iterator_next_return ctf_fs_iterator_next(
                struct bt_private_notification_iterator *iterator)
 {
-       struct ctf_fs_stream *fs_stream =
+       struct bt_notification_iterator_next_return next_ret;
+       struct ctf_fs_notif_iter_data *notif_iter_data =
                bt_private_notification_iterator_get_user_data(iterator);
+       int ret;
+
+       assert(notif_iter_data->ds_file);
+       next_ret = ctf_fs_ds_file_next(notif_iter_data->ds_file);
+       if (next_ret.status == BT_NOTIFICATION_ITERATOR_STATUS_END) {
+               assert(!next_ret.notification);
+               notif_iter_data->ds_file_info_index++;
+
+               if (notif_iter_data->ds_file_info_index ==
+                               notif_iter_data->ds_file_group->ds_file_infos->len) {
+                       /*
+                        * No more stream files to read: we reached the
+                        * real end.
+                        */
+                       goto end;
+               }
+
+               /*
+                * Open and start reading the next stream file within
+                * our stream file group.
+                */
+               ret = notif_iter_data_set_current_ds_file(notif_iter_data);
+               if (ret) {
+                       next_ret.status = BT_NOTIFICATION_ITERATOR_STATUS_ERROR;
+                       goto end;
+               }
+
+               next_ret = ctf_fs_ds_file_next(notif_iter_data->ds_file);
+
+               /*
+                * We should not get BT_NOTIFICATION_ITERATOR_STATUS_END
+                * with a brand new stream file because empty stream
+                * files are not even part of stream file groups, which
+                * means we're sure to get at least one pair of "packet
+                * begin" and "packet end" notifications in the case of
+                * a single, empty packet.
+                */
+               assert(next_ret.status != BT_NOTIFICATION_ITERATOR_STATUS_END);
+       }
 
-       return ctf_fs_stream_next(fs_stream);
+end:
+       return next_ret;
 }
 
 void ctf_fs_iterator_finalize(struct bt_private_notification_iterator *it)
 {
-       void *ctf_fs_stream =
+       void *notif_iter_data =
                bt_private_notification_iterator_get_user_data(it);
 
-       ctf_fs_stream_destroy(ctf_fs_stream);
+       ctf_fs_notif_iter_data_destroy(notif_iter_data);
 }
 
 enum bt_notification_iterator_status ctf_fs_iterator_init(
                struct bt_private_notification_iterator *it,
                struct bt_private_port *port)
 {
-       struct ctf_fs_stream *ctf_fs_stream = NULL;
        struct ctf_fs_port_data *port_data;
+       struct ctf_fs_notif_iter_data *notif_iter_data = NULL;
        enum bt_notification_iterator_status ret =
                BT_NOTIFICATION_ITERATOR_STATUS_OK;
+       int iret;
 
        port_data = bt_private_port_get_user_data(port);
        if (!port_data) {
@@ -86,18 +167,25 @@ enum bt_notification_iterator_status ctf_fs_iterator_init(
                goto error;
        }
 
-       ctf_fs_stream = ctf_fs_stream_create(port_data->ctf_fs_trace,
-               port_data->path->str);
-       if (!ctf_fs_stream) {
+       notif_iter_data = g_new0(struct ctf_fs_notif_iter_data, 1);
+       if (!notif_iter_data) {
+               ret = BT_NOTIFICATION_ITERATOR_STATUS_NOMEM;
+               goto error;
+       }
+
+       notif_iter_data->ds_file_group = port_data->ds_file_group;
+       iret = notif_iter_data_set_current_ds_file(notif_iter_data);
+       if (iret) {
+               ret = BT_NOTIFICATION_ITERATOR_STATUS_ERROR;
                goto error;
        }
 
-       ret = bt_private_notification_iterator_set_user_data(it, ctf_fs_stream);
+       ret = bt_private_notification_iterator_set_user_data(it, notif_iter_data);
        if (ret) {
                goto error;
        }
 
-       ctf_fs_stream = NULL;
+       notif_iter_data = NULL;
        goto end;
 
 error:
@@ -108,7 +196,7 @@ error:
        }
 
 end:
-       ctf_fs_stream_destroy(ctf_fs_stream);
+       ctf_fs_notif_iter_data_destroy(notif_iter_data);
        return ret;
 }
 
@@ -139,6 +227,10 @@ void ctf_fs_trace_destroy(void *data)
                return;
        }
 
+       if (ctf_fs_trace->ds_file_groups) {
+               g_ptr_array_free(ctf_fs_trace->ds_file_groups, TRUE);
+       }
+
        if (ctf_fs_trace->path) {
                g_string_free(ctf_fs_trace->path, TRUE);
        }
@@ -171,30 +263,34 @@ void port_data_destroy(void *data) {
                return;
        }
 
-       if (port_data->path) {
-               g_string_free(port_data->path, TRUE);
-       }
-
        g_free(port_data);
 }
 
 static
 int create_one_port_for_trace(struct ctf_fs_trace *ctf_fs_trace,
-               const char *stream_path)
+               struct ctf_fs_ds_file_group *ds_file_group)
 {
        int ret = 0;
        struct bt_private_port *port = NULL;
        struct ctf_fs_port_data *port_data = NULL;
        GString *port_name = NULL;
        struct ctf_fs_component *ctf_fs = ctf_fs_trace->ctf_fs;
+       struct ctf_fs_ds_file_info *ds_file_info =
+               g_ptr_array_index(ds_file_group->ds_file_infos, 0);
 
        port_name = g_string_new(NULL);
        if (!port_name) {
                goto error;
        }
 
-       /* Assign the name for the new output port */
-       g_string_printf(port_name, "%s", stream_path);
+       /*
+        * Assign the name for the new output port. If there's more than
+        * one stream file in the stream file group, the first
+        * (earliest) stream file's path is used.
+        */
+       assert(ds_file_group->ds_file_infos->len > 0);
+       ds_file_info = g_ptr_array_index(ds_file_group->ds_file_infos, 0);
+       g_string_assign(port_name, ds_file_info->path->str);
        PDBG("Creating one port named `%s`\n", port_name->str);
 
        /* Create output port for this file */
@@ -203,12 +299,7 @@ int create_one_port_for_trace(struct ctf_fs_trace *ctf_fs_trace,
                goto error;
        }
 
-       port_data->ctf_fs_trace = ctf_fs_trace;
-       port_data->path = g_string_new(stream_path);
-       if (!port_data->path) {
-               goto error;
-       }
-
+       port_data->ds_file_group = ds_file_group;
        port = bt_private_component_source_add_output_private_port(
                ctf_fs->priv_comp, port_name->str, port_data);
        if (!port) {
@@ -234,15 +325,434 @@ end:
 
 static
 int create_ports_for_trace(struct ctf_fs_trace *ctf_fs_trace)
+{
+       int ret = 0;
+       struct ctf_fs_component *ctf_fs = ctf_fs_trace->ctf_fs;
+       size_t i;
+
+       /* Create one output port for each stream file group */
+       for (i = 0; i < ctf_fs_trace->ds_file_groups->len; i++) {
+               struct ctf_fs_ds_file_group *ds_file_group =
+                       g_ptr_array_index(ctf_fs_trace->ds_file_groups, i);
+
+               ret = create_one_port_for_trace(ctf_fs_trace, ds_file_group);
+               if (ret) {
+                       PERR("Cannot create output port.\n");
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
+uint64_t get_packet_header_stream_instance_id(struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_field *packet_header_field)
+{
+       struct bt_ctf_field *stream_instance_id_field = NULL;
+       uint64_t stream_instance_id = -1ULL;
+       int ret;
+
+       if (!packet_header_field) {
+               goto end;
+       }
+
+       stream_instance_id_field = bt_ctf_field_structure_get_field_by_name(
+               packet_header_field, "stream_instance_id");
+       if (!stream_instance_id_field) {
+               goto end;
+       }
+
+       ret = bt_ctf_field_unsigned_integer_get_value(stream_instance_id_field,
+               &stream_instance_id);
+       if (ret) {
+               stream_instance_id = -1ULL;
+               goto end;
+       }
+
+end:
+       bt_put(stream_instance_id_field);
+       return stream_instance_id;
+}
+
+struct bt_ctf_stream_class *stream_class_from_packet_header(
+               struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_field *packet_header_field)
+{
+       struct bt_ctf_field *stream_id_field = NULL;
+       struct bt_ctf_stream_class *stream_class = NULL;
+       uint64_t stream_id = -1ULL;
+       int ret;
+
+       if (!packet_header_field) {
+               goto single_stream_class;
+       }
+
+       stream_id_field = bt_ctf_field_structure_get_field_by_name(
+               packet_header_field, "stream_id");
+       if (!stream_id_field) {
+               goto end;
+       }
+
+       ret = bt_ctf_field_unsigned_integer_get_value(stream_id_field,
+               &stream_id);
+       if (ret) {
+               stream_id = -1ULL;
+       }
+
+       if (stream_id == -1ULL) {
+single_stream_class:
+               /* Single stream class */
+               if (bt_ctf_trace_get_stream_class_count(
+                               ctf_fs_trace->metadata->trace) == 0) {
+                       goto end;
+               }
+
+               stream_class = bt_ctf_trace_get_stream_class_by_index(
+                       ctf_fs_trace->metadata->trace, 0);
+       } else {
+               stream_class = bt_ctf_trace_get_stream_class_by_id(
+                       ctf_fs_trace->metadata->trace, stream_id);
+       }
+
+end:
+       bt_put(stream_id_field);
+       return stream_class;
+}
+
+uint64_t get_packet_context_timestamp_begin_ns(
+               struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_field *packet_context_field)
+{
+       int ret;
+       struct bt_ctf_field *timestamp_begin_field = NULL;
+       struct bt_ctf_field_type *timestamp_begin_ft = NULL;
+       uint64_t timestamp_begin_raw_value = -1ULL;
+       uint64_t timestamp_begin_ns = -1ULL;
+       int64_t timestamp_begin_ns_signed;
+       struct bt_ctf_clock_class *timestamp_begin_clock_class = NULL;
+       struct bt_ctf_clock_value *clock_value = NULL;
+
+       if (!packet_context_field) {
+               goto end;
+       }
+
+       timestamp_begin_field = bt_ctf_field_structure_get_field_by_name(
+               packet_context_field, "timestamp_begin");
+       if (!timestamp_begin_field) {
+               goto end;
+       }
+
+       timestamp_begin_ft = bt_ctf_field_get_type(timestamp_begin_field);
+       assert(timestamp_begin_ft);
+       timestamp_begin_clock_class =
+               bt_ctf_field_type_integer_get_mapped_clock_class(timestamp_begin_ft);
+       if (!timestamp_begin_clock_class) {
+               goto end;
+       }
+
+       ret = bt_ctf_field_unsigned_integer_get_value(timestamp_begin_field,
+               &timestamp_begin_raw_value);
+       if (ret) {
+               goto end;
+       }
+
+       clock_value = bt_ctf_clock_value_create(timestamp_begin_clock_class,
+               timestamp_begin_raw_value);
+       if (!clock_value) {
+               goto end;
+       }
+
+       ret = bt_ctf_clock_value_get_value_ns_from_epoch(clock_value,
+               &timestamp_begin_ns_signed);
+       if (ret) {
+               goto end;
+       }
+
+       timestamp_begin_ns = (uint64_t) timestamp_begin_ns_signed;
+
+end:
+       bt_put(timestamp_begin_field);
+       bt_put(timestamp_begin_ft);
+       bt_put(timestamp_begin_clock_class);
+       bt_put(clock_value);
+       return timestamp_begin_ns;
+}
+
+static
+void ctf_fs_ds_file_info_destroy(struct ctf_fs_ds_file_info *ds_file_info)
+{
+       if (!ds_file_info) {
+               return;
+       }
+
+       if (ds_file_info->path) {
+               g_string_free(ds_file_info->path, TRUE);
+       }
+
+       g_free(ds_file_info);
+}
+
+static
+struct ctf_fs_ds_file_info *ctf_fs_ds_file_info_create(const char *path,
+               uint64_t begin_ns)
+{
+       struct ctf_fs_ds_file_info *ds_file_info;
+
+       ds_file_info = g_new0(struct ctf_fs_ds_file_info, 1);
+       if (!ds_file_info) {
+               goto end;
+       }
+
+       ds_file_info->path = g_string_new(path);
+       if (!ds_file_info->path) {
+               ctf_fs_ds_file_info_destroy(ds_file_info);
+               ds_file_info = NULL;
+               goto end;
+       }
+
+       ds_file_info->begin_ns = begin_ns;
+
+end:
+       return ds_file_info;
+}
+
+static
+void ctf_fs_ds_file_group_destroy(struct ctf_fs_ds_file_group *ds_file_group)
+{
+       if (!ds_file_group) {
+               return;
+       }
+
+       if (ds_file_group->ds_file_infos) {
+               g_ptr_array_free(ds_file_group->ds_file_infos, TRUE);
+       }
+
+       bt_put(ds_file_group->stream);
+       g_free(ds_file_group);
+}
+
+static
+struct ctf_fs_ds_file_group *ctf_fs_ds_file_group_create(
+               struct ctf_fs_trace *ctf_fs_trace,
+               struct bt_ctf_stream_class *stream_class,
+               uint64_t stream_instance_id)
+{
+       struct ctf_fs_ds_file_group *ds_file_group;
+
+       assert(stream_class);
+       ds_file_group = g_new0(struct ctf_fs_ds_file_group, 1);
+       if (!ds_file_group) {
+               goto error;
+       }
+
+       ds_file_group->ds_file_infos = g_ptr_array_new_with_free_func(
+               (GDestroyNotify) ctf_fs_ds_file_info_destroy);
+       if (!ds_file_group->ds_file_infos) {
+               goto error;
+       }
+
+       if (stream_instance_id == -1ULL) {
+               ds_file_group->stream = bt_ctf_stream_create(
+                       stream_class, NULL);
+       } else {
+               ds_file_group->stream = bt_ctf_stream_create_with_id(
+                       stream_class, NULL, stream_instance_id);
+       }
+
+       if (!ds_file_group->stream) {
+               goto error;
+       }
+
+       ds_file_group->ctf_fs_trace = ctf_fs_trace;
+
+       goto end;
+
+error:
+       ctf_fs_ds_file_group_destroy(ds_file_group);
+       ds_file_group = NULL;
+
+end:
+       return ds_file_group;
+}
+
+static
+int ctf_fs_ds_file_group_add_ds_file_info(
+               struct ctf_fs_ds_file_group *ds_file_group,
+               const char *path, uint64_t begin_ns)
+{
+       struct ctf_fs_ds_file_info *ds_file_info;
+       gint i = 0;
+       int ret = 0;
+
+       ds_file_info = ctf_fs_ds_file_info_create(path, begin_ns);
+       if (!ds_file_info) {
+               goto error;
+       }
+
+       /* Find a spot to insert this one */
+       for (i = 0; i < ds_file_group->ds_file_infos->len; i++) {
+               struct ctf_fs_ds_file_info *other_ds_file_info =
+                       g_ptr_array_index(ds_file_group->ds_file_infos, i);
+
+               if (begin_ns < other_ds_file_info->begin_ns) {
+                       break;
+               }
+       }
+
+       if (i == ds_file_group->ds_file_infos->len) {
+               /* Append instead */
+               i = -1;
+       }
+
+       g_ptr_array_insert(ds_file_group->ds_file_infos, i, ds_file_info);
+       ds_file_info = NULL;
+       goto end;
+
+error:
+       ctf_fs_ds_file_info_destroy(ds_file_info);
+       ret = -1;
+
+end:
+       return ret;
+}
+
+static
+int add_ds_file_to_ds_file_group(struct ctf_fs_trace *ctf_fs_trace,
+               const char *path)
+{
+       struct bt_ctf_field *packet_header_field = NULL;
+       struct bt_ctf_field *packet_context_field = NULL;
+       struct bt_ctf_stream_class *stream_class = NULL;
+       struct ctf_fs_component *ctf_fs = ctf_fs_trace->ctf_fs;
+       uint64_t stream_instance_id = -1ULL;
+       uint64_t begin_ns = -1ULL;
+       struct ctf_fs_ds_file_group *ds_file_group = NULL;
+       bool add_group = false;
+       int ret;
+       size_t i;
+
+       ret = ctf_fs_ds_file_get_packet_header_context_fields(
+               ctf_fs_trace, path, &packet_header_field,
+               &packet_context_field);
+       if (ret) {
+               PERR("Cannot get stream file's first packet's header and context fields (`%s`).\n",
+                       path);
+               goto error;
+       }
+
+       stream_instance_id = get_packet_header_stream_instance_id(ctf_fs_trace,
+               packet_header_field);
+       begin_ns = get_packet_context_timestamp_begin_ns(ctf_fs_trace,
+               packet_context_field);
+       stream_class = stream_class_from_packet_header(ctf_fs_trace,
+               packet_header_field);
+       if (!stream_class) {
+               goto error;
+       }
+
+       if (begin_ns == -1ULL) {
+               /*
+                * No beggining timestamp to sort the stream files
+                * within a stream file group, so consider that this
+                * file must be the only one within its group.
+                */
+               stream_instance_id = -1ULL;
+       }
+
+       if (stream_instance_id == -1ULL) {
+               /*
+                * No stream instance ID or no beginning timestamp:
+                * create a unique stream file group for this stream
+                * file because, even if there's a stream instance ID,
+                * there's no timestamp to order the file within its
+                * group.
+                */
+
+               ds_file_group = ctf_fs_ds_file_group_create(ctf_fs_trace,
+                       stream_class, stream_instance_id);
+               if (!ds_file_group) {
+                       goto error;
+               }
+
+               ret = ctf_fs_ds_file_group_add_ds_file_info(ds_file_group,
+                               path, begin_ns);
+               if (ret) {
+                       goto error;
+               }
+
+               add_group = true;
+               goto end;
+       }
+
+       assert(stream_instance_id != -1ULL);
+       assert(begin_ns != -1ULL);
+
+       /* Find an existing stream file group with this ID */
+       for (i = 0; i < ctf_fs_trace->ds_file_groups->len; i++) {
+               int64_t id;
+               struct bt_ctf_stream_class *cand_stream_class;
+
+               ds_file_group = g_ptr_array_index(
+                       ctf_fs_trace->ds_file_groups, i);
+               id = bt_ctf_stream_get_id(ds_file_group->stream);
+               cand_stream_class = bt_ctf_stream_get_class(
+                       ds_file_group->stream);
+
+               assert(cand_stream_class);
+               bt_put(cand_stream_class);
+
+               if (cand_stream_class == stream_class &&
+                               (uint64_t) id == stream_instance_id) {
+                       break;
+               }
+
+               ds_file_group = NULL;
+       }
+
+       if (!ds_file_group) {
+               ds_file_group = ctf_fs_ds_file_group_create(ctf_fs_trace,
+                       stream_class, stream_instance_id);
+               if (!ds_file_group) {
+                       goto error;
+               }
+
+               add_group = true;
+       }
+
+       ret = ctf_fs_ds_file_group_add_ds_file_info(ds_file_group,
+                       path, begin_ns);
+       if (ret) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       ctf_fs_ds_file_group_destroy(ds_file_group);
+       ret = -1;
+
+end:
+       if (add_group && ds_file_group) {
+               g_ptr_array_add(ctf_fs_trace->ds_file_groups, ds_file_group);
+       }
+
+       bt_put(packet_header_field);
+       bt_put(packet_context_field);
+       bt_put(stream_class);
+       return ret;
+}
+
+static
+int create_ds_file_groups(struct ctf_fs_trace *ctf_fs_trace)
 {
        int ret = 0;
        const char *basename;
        GError *error = NULL;
        GDir *dir = NULL;
-       struct ctf_fs_file *file = NULL;
        struct ctf_fs_component *ctf_fs = ctf_fs_trace->ctf_fs;
 
-       /* Create one output port for each stream file */
+       /* Check each file in the path directory, except specific ones */
        dir = g_dir_open(ctf_fs_trace->path->str, 0, &error);
        if (!dir) {
                PERR("Cannot open directory `%s`: %s (code %d)\n",
@@ -252,6 +762,8 @@ int create_ports_for_trace(struct ctf_fs_trace *ctf_fs_trace)
        }
 
        while ((basename = g_dir_read_name(dir))) {
+               struct ctf_fs_file *file;
+
                if (!strcmp(basename, CTF_FS_METADATA_FILENAME)) {
                        /* Ignore the metadata stream. */
                        PDBG("Ignoring metadata file `%s/%s`\n",
@@ -294,19 +806,19 @@ int create_ports_for_trace(struct ctf_fs_trace *ctf_fs_trace)
                        /* Skip empty stream. */
                        PDBG("Ignoring empty file `%s`\n", file->path->str);
                        ctf_fs_file_destroy(file);
-                       file = NULL;
                        continue;
                }
 
-               ret = create_one_port_for_trace(ctf_fs_trace, file->path->str);
+               ret = add_ds_file_to_ds_file_group(ctf_fs_trace,
+                       file->path->str);
                if (ret) {
-                       PERR("Cannot create output port for file `%s`\n",
+                       PDBG("Cannot add stream file `%s` to stream file group\n",
                                file->path->str);
+                       ctf_fs_file_destroy(file);
                        goto error;
                }
 
                ctf_fs_file_destroy(file);
-               file = NULL;
        }
 
        goto end;
@@ -324,7 +836,6 @@ end:
                g_error_free(error);
        }
 
-       ctf_fs_file_destroy(file);
        return ret;
 }
 
@@ -393,11 +904,22 @@ struct ctf_fs_trace *ctf_fs_trace_create(struct ctf_fs_component *ctf_fs,
                goto error;
        }
 
+       ctf_fs_trace->ds_file_groups = g_ptr_array_new_with_free_func(
+               (GDestroyNotify) ctf_fs_ds_file_group_destroy);
+       if (!ctf_fs_trace->ds_file_groups) {
+               goto error;
+       }
+
        ret = ctf_fs_metadata_set_trace(ctf_fs_trace);
        if (ret) {
                goto error;
        }
 
+       ret = create_ds_file_groups(ctf_fs_trace);
+       if (ret) {
+               goto error;
+       }
+
        ret = create_cc_prio_map(ctf_fs_trace);
        if (ret) {
                goto error;
index 63b299a09288bae6187b3bd6eaf389628eb52cd2..02a5edfb53612731e6bdcd5bd448acba59ae0ccb 100644 (file)
 #include <babeltrace/babeltrace-internal.h>
 #include <babeltrace/graph/component.h>
 #include <babeltrace/graph/clock-class-priority-map.h>
-#include "data-stream.h"
+#include "data-stream-file.h"
 
 BT_HIDDEN
 extern bool ctf_fs_debug;
 
 struct ctf_fs_file {
-       /* Weak */
+       /* Weak, belongs to component */
        struct ctf_fs_component *ctf_fs;
 
        /* Owned by this */
@@ -62,7 +62,12 @@ struct ctf_fs_metadata {
        int bo;
 };
 
-struct ctf_fs_stream {
+struct ctf_fs_ds_file_info {
+       GString *path;
+       uint64_t begin_ns;
+};
+
+struct ctf_fs_ds_file {
        /* Owned by this */
        struct ctf_fs_file *file;
 
@@ -100,7 +105,7 @@ struct ctf_fs_component_options {
 };
 
 struct ctf_fs_component {
-       /* Weak */
+       /* Weak, guaranteed to exist */
        struct bt_private_component *priv_comp;
 
        /* Array of struct ctf_fs_port_data *, owned by this */
@@ -115,7 +120,7 @@ struct ctf_fs_component {
 };
 
 struct ctf_fs_trace {
-       /* Weak */
+       /* Weak, belongs to component */
        struct ctf_fs_component *ctf_fs;
 
        /* Owned by this */
@@ -124,6 +129,9 @@ struct ctf_fs_trace {
        /* Owned by this */
        struct bt_clock_class_priority_map *cc_prio_map;
 
+       /* Array of struct ctf_fs_ds_file_group *, owned by this */
+       GPtrArray *ds_file_groups;
+
        /* Owned by this */
        GString *path;
 
@@ -131,14 +139,41 @@ struct ctf_fs_trace {
        GString *name;
 };
 
-struct ctf_fs_port_data {
+struct ctf_fs_ds_file_group {
+       /*
+        * Array of struct ctf_fs_ds_file_info, owned by this.
+        *
+        * This is an _ordered_ array of data stream file infos which
+        * belong to this group (a single stream instance).
+        *
+        * You can call ctf_fs_ds_file_create() with one of those paths
+        * and the CTF IR stream below.
+        */
+       GPtrArray *ds_file_infos;
+
        /* Owned by this */
-       GString *path;
+       struct bt_ctf_stream *stream;
 
-       /* Weak */
+       /* Weak, belongs to component */
        struct ctf_fs_trace *ctf_fs_trace;
 };
 
+struct ctf_fs_port_data {
+       /* Weak, belongs to ctf_fs_trace */
+       struct ctf_fs_ds_file_group *ds_file_group;
+};
+
+struct ctf_fs_notif_iter_data {
+       /* Weak, belongs to ctf_fs_trace */
+       struct ctf_fs_ds_file_group *ds_file_group;
+
+       /* Owned by this */
+       struct ctf_fs_ds_file *ds_file;
+
+       /* Which file the iterator is _currently_ operating on */
+       size_t ds_file_info_index;
+};
+
 BT_HIDDEN
 enum bt_component_status ctf_fs_init(struct bt_private_component *source,
                struct bt_value *params, void *init_method_data);
This page took 0.045777 seconds and 4 git commands to generate.