summaryrefslogtreecommitdiff
path: root/ioreplay/src/generate
diff options
context:
space:
mode:
authorPaul Bütow <pbuetow@mimecast.com>2018-03-01 11:21:26 +0000
committerPaul Bütow <pbuetow@mimecast.com>2018-03-01 11:21:26 +0000
commit56f8cdff9aaa9bf00c5dc9441a7569374f2cbafb (patch)
treeb5b440b504b9879e241733fa38d19089fb3377b2 /ioreplay/src/generate
initial commit0.1
Diffstat (limited to 'ioreplay/src/generate')
-rw-r--r--ioreplay/src/generate/generate.c235
-rw-r--r--ioreplay/src/generate/generate.h112
-rw-r--r--ioreplay/src/generate/gioop.c838
-rw-r--r--ioreplay/src/generate/gioop.h102
-rw-r--r--ioreplay/src/generate/gparser.c356
-rw-r--r--ioreplay/src/generate/gparser.h113
-rw-r--r--ioreplay/src/generate/gprocess.c101
-rw-r--r--ioreplay/src/generate/gprocess.h90
-rw-r--r--ioreplay/src/generate/gtask.c91
-rw-r--r--ioreplay/src/generate/gtask.h100
-rw-r--r--ioreplay/src/generate/gwriter.c85
-rw-r--r--ioreplay/src/generate/gwriter.h86
-rw-r--r--ioreplay/src/generate/vsize.c247
-rw-r--r--ioreplay/src/generate/vsize.h180
14 files changed, 2736 insertions, 0 deletions
diff --git a/ioreplay/src/generate/generate.c b/ioreplay/src/generate/generate.c
new file mode 100644
index 0000000..ff1be94
--- /dev/null
+++ b/ioreplay/src/generate/generate.c
@@ -0,0 +1,235 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "generate.h"
+
+#include "../meta/meta.h"
+#include "gtask.h"
+#include "gwriter.h"
+#include "gparser.h"
+
+#include <fcntl.h>
+
+#define _MAX_PROCESSES 1024*1024*10
+
+#define _Perc_filtered (g->num_lines_filtered / (g->lineno/100.0))
+
+generate_s* generate_new(options_s *opts)
+{
+ generate_s *g = Malloc(generate_s);
+
+ g->writer = NULL;
+ g->lineno = 0;
+ g->name = opts->name;
+ g->replay_fd = NULL;
+ g->mps = mounts_new(opts);
+ g->num_lines_filtered = 0;
+ g->num_vsizes = 0;
+ g->start_time = -1;
+ g->pid_map = amap_new(_MAX_PROCESSES);
+ g->vsize_map = hmap_new(_MAX_PROCESSES);
+ g->mmap_map = hmap_new(1024*1024);
+ g->vfd_buffer = rbuffer_new(1024);
+ g->num_mapped_pids = 0;
+ g->num_mapped_fds = 10;
+ g->opts = opts;
+ g->reuse_queue = rbuffer_new(1024);
+ g->replay_fd = Fopen(opts->replay_file, "w");
+
+ return g;
+}
+
+void generate_destroy(generate_s *g)
+{
+ // TODO: Also clean the contets of these maps
+ amap_destroy(g->pid_map);
+ hmap_destroy(g->vsize_map);
+ hmap_destroy(g->mmap_map);
+ rbuffer_destroy(g->vfd_buffer);
+ mounts_destroy(g->mps);
+
+ gtask_s *task = NULL;
+ while (NULL != (task = rbuffer_get_next(g->reuse_queue)))
+ gtask_destroy(task);
+ rbuffer_destroy(g->reuse_queue);
+
+ fclose(g->replay_fd);
+ free(g);
+}
+
+
+status_e generate_run(options_s *opts)
+{
+ generate_s *g = generate_new(opts);
+ Put("Parsing file %s, writing output to %s", opts->capture_file,
+ opts->replay_file);
+ FILE *capture_fd = Fopen(opts->capture_file, "r");
+
+ size_t len = 0;
+ ssize_t read;
+ char *line = NULL;
+
+ drop_root(opts->user);
+
+ // Reserve first few bytes for meta information
+ meta_s *meta = meta_new(g->replay_fd);
+ meta_reserve(meta);
+
+ // The writer will write the .replay file
+ gwriter_s *writer = gwriter_new(g);
+
+ // The parser will parse every line of the .capture file
+ gparser_s *parser = gparser_new(g);
+
+ g->writer = writer;
+
+ // Start one writer and one parser thread!
+ gparser_start(parser);
+ gwriter_start(writer);
+
+ Out("Processing, it may take a while: ");
+
+ // Process each line of the .capture file. Determine line by line whether
+ // the I/O operation makes sense or not. It might be that SystemTap skipped
+ // some I/O ops due to system overload or other issues. The result is that
+ // some lines may be corrupt or contain I/O operations on unknown file
+ // handles. It could also be that there are operations on unknown
+ // file handles such as sockets etc. These will be all filtered out by
+ // either the parser or the writer thread!
+
+ while ((read = getline(&line, &len, capture_fd)) != -1) {
+ if (0 > ++g->lineno) {
+ Error("lineno:%lu Line number overflow", g->lineno);
+ }
+ if (strlen(line) >= MAX_LINE_LEN) {
+ Error("lineno:%lu Exceeded max line length", g->lineno);
+ }
+
+ // Create a new generate task (try to reuse a task object)...
+ gtask_s *t = rbuffer_get_next(g->reuse_queue);
+ if (!t) {
+ t = gtask_new(g);
+ } else if (t->ret != 0) {
+ g->num_lines_filtered++;
+ }
+ gtask_init(t, line, g->lineno);
+
+ // ...pass it to the parser queue
+ while (!rbuffer_insert(parser->queue, t))
+ usleep(100);
+
+ if (g->lineno % 1000000 == 0) {
+ Out(" %lu (filtered:%.2lf%%)", g->lineno, _Perc_filtered);
+ }
+ }
+
+ Put("\nDone reading input file!");
+
+ Put("Waiting for parser thread...");
+ gparser_terminate(parser);
+ gparser_destroy(parser);
+
+ Put("Waiting for writer thread...");
+ gwriter_terminate(writer);
+ gwriter_destroy(writer);
+
+ // Retrieve all left over processed tasks to collect the
+ // statistics!
+ gtask_s *t;
+ while (NULL != (t = rbuffer_get_next(g->reuse_queue))) {
+ if (t->ret != 0)
+ g->num_lines_filtered++;
+ gtask_destroy(t);
+ }
+
+ Put("Processed %lu lines in total, had to filter out %.2lf%%",
+ g->lineno, _Perc_filtered);
+
+ Put("Writing init section to '%s'...", opts->replay_file);
+ fprintf(g->replay_fd, "#INIT\n");
+ off_t init_offset = ftello(g->replay_fd);
+ hmap_run_cb(g->vsize_map, generate_write_init_cb);
+
+ Put("Writing meta header to '%s'...", opts->replay_file);
+ meta_write_start(meta);
+
+ // The meta header is being written to the first line of the .replay
+ // file and used by ioreplay to do various things (e.g. initializing
+ // the test correctly, creating the internal data structures with the
+ // correct sizes etc.
+
+ meta_write_l(meta, "replay_version", REPLAY_VERSION);
+ meta_write_l(meta, "init_offset", init_offset);
+
+ meta_write_s(meta, "user", opts->user);
+ meta_write_s(meta, "name", opts->name);
+
+ meta_write_l(meta, "num_vsizes", g->num_vsizes);
+ meta_write_l(meta, "num_mapped_pids", g->num_mapped_pids);
+ meta_write_l(meta, "num_mapped_fds", g->num_mapped_fds);
+ meta_write_l(meta, "num_lines", g->lineno - g->num_lines_filtered);
+
+ meta_destroy(meta);
+ fclose(capture_fd);
+
+ Put("Generating '%s' done", opts->replay_file);
+ generate_destroy(g);
+
+ return SUCCESS;
+}
+
+void generate_write_init_cb(void *data)
+{
+ vsize_s *l = data;
+ generate_s *g = l->generate;
+
+ if (l->required && strlen(l->path) > 0) {
+ fprintf(g->replay_fd, "%d|%d|%ld|%s|\n",
+ l->is_dir, l->is_file, -l->vsize_deficit, l->path);
+ }
+}
+
+vsize_s* generate_vsize_by_path(generate_s *g, gtask_s *t,
+ char *path)
+{
+ vsize_s *v = NULL;
+
+ if (!path && t)
+ path = t->path;
+
+ Error_if(!path, "No path specified");
+ v = hmap_get(g->vsize_map, path);
+
+ if (!v) {
+ v = vsize_new(path, ++g->num_vsizes, g);
+ hmap_insert(g->vsize_map, path, v);
+ }
+
+ if (t)
+ t->vsize = v;
+
+ return v;
+}
+
+void generate_gprocess_by_realpid(generate_s *g, gtask_s *t)
+{
+ // Get the virtual process data object from the virtual PID space.
+ t->gprocess = amap_get(g->pid_map, t->pid);
+ if (t->gprocess == NULL) {
+ t->gprocess = gprocess_new(t->pid, ++g->num_mapped_pids);
+ if (amap_set(g->pid_map, t->pid, t->gprocess)) {
+ Error("lineno:%lu Can not insert PID %ld", t->lineno, t->pid);
+ }
+ }
+}
diff --git a/ioreplay/src/generate/generate.h b/ioreplay/src/generate/generate.h
new file mode 100644
index 0000000..cf096d2
--- /dev/null
+++ b/ioreplay/src/generate/generate.h
@@ -0,0 +1,112 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GENERATE_H
+#define GENERATE_H
+
+#include "gwriter.h"
+#include "../datas/amap.h"
+#include "../datas/hmap.h"
+#include "../datas/rbuffer.h"
+#include "../defaults.h"
+#include "../mounts.h"
+#include "../options.h"
+
+// Forward declarations (header include hell)
+struct gtask_s_;
+
+/**
+ * @brief The generate object definition
+ *
+ * This is the general data structure required to generate a .replay file from
+ * the .capture file.
+ */
+typedef struct generate_s_ {
+ long lineno; /**< The current line number */
+ long num_lines_filtered; /**< The amount of lines filtered out */
+ long start_time; /**< The start time from the .capture file */
+ char *name; /**< The name of the test specified by the user */
+ FILE *replay_fd; /**< The fd of the .replay file */
+ mounts_s *mps; /**< The mounts object */
+ hmap_s *mmap_map; /**< mmap address mappings */
+ amap_s *pid_map; /**< A map of all virtual process objects */
+ unsigned long num_mapped_pids; /**< The amount of mapped PIDs */
+ unsigned long num_mapped_fds; /**< The amount of mapped FDs */
+ hmap_s *vsize_map; /**< A hash map of all virtual size objects */
+ unsigned long num_vsizes; /**< The amount of virtual sizes */
+ options_s *opts; /**< A pointer to the options object */
+ rbuffer_s *vfd_buffer; /**< A virtual fd buffer, for reusing these */
+ rbuffer_s *reuse_queue; /**< A task buffer, for reusing these */
+ struct gwriter_s_ *writer; /**< A pointer to the writer object */
+} generate_s;
+
+/**
+ * @brief Creates a new generate object
+ *
+ * @param opts The options object
+ * @return The new generate object
+ */
+generate_s* generate_new(options_s *opts);
+
+/**
+ * @brief Destroys a generate object
+ *
+ * @param g The generate object to destroy
+ */
+void generate_destroy(generate_s* g);
+
+/**
+ * @brief Generates a .replay file from a .capture file
+ *
+ * @param opts The options object
+ * @return SUCCESS on success
+ */
+status_e generate_run(options_s *opts);
+
+/**
+ * @brief Callback to write the INIT section to the .replay file
+ *
+ * This function writes a list of all pre-required
+ * paths to the .replay file. That then can be used
+ * by ioreplay to initialise the test enironment.
+ *
+ * @param data A pointer to the vsize timestamp object
+ */
+void generate_write_init_cb(void *data);
+
+/**
+ * @brief Retrieves the virtual size object of a given path
+ *
+ * A new one will be created in case there is no such virtual size object yet.
+ *
+ * @param g The generate object
+ * @param t The task object (vfd will be stored to t->vfd)
+ * @param path The file path
+ * @return The virtual size object
+ */
+vsize_s* generate_vsize_by_path(generate_s *g, struct gtask_s_ *t,
+ char *path);
+
+/**
+ * @brief Retrieves the virtual process object of a given real PID
+ *
+ * A new one will be created in case there is no such virtual process object
+ * yet.
+ *
+ * @param g The generate object
+ * @param t The task object (vfd will be stored to t->gprocess)
+ */
+void generate_gprocess_by_realpid(generate_s *g, struct gtask_s_ *t);
+
+#endif // GENERATE_H
diff --git a/ioreplay/src/generate/gioop.c b/ioreplay/src/generate/gioop.c
new file mode 100644
index 0000000..01701bc
--- /dev/null
+++ b/ioreplay/src/generate/gioop.c
@@ -0,0 +1,838 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gioop.h"
+
+status_e gioop_run(gwriter_s *w, gtask_s *t)
+{
+ status_e ret = SUCCESS;
+
+ // There was already an error in the parser (parser.c) processing this
+ // task! Don't process it futher.
+ if (t->ret != SUCCESS) {
+ Cleanup(t->ret);
+ }
+
+ generate_s *g = w->generate;
+
+ // Get the virtual process data object from the virtual PID space and store
+ // a pointer to it to t->gprocess
+ generate_gprocess_by_realpid(g, t);
+
+ // One of the open syscalls may openes a file handle succesfully
+ if (Eq(t->op, "open")) {
+ Cleanup(gioop_open(w, t, g));
+
+ } else if (Eq(t->op, "openat")) {
+ Cleanup(gioop_openat(w, t, g));
+
+ } else if (Eq(t->op, "creat")) {
+ Cleanup(gioop_creat(w, t, g));
+ }
+
+ // Get the virtual file descriptor of a given real fd and store a pointer
+ // to it to t->vfd.
+ if (t->has_fd) {
+ ret = gprocess_vfd_by_realfd(t->gprocess, t);
+ Cleanup_unless(SUCCESS, ret);
+ }
+
+
+ if (Eq(t->op, "close")) {
+ Cleanup(gioop_close(w, t, g));
+
+ } else if (Eq(t->op, "stat")) {
+ Cleanup(gioop_stat(w, t, g));
+
+ } else if (Eq(t->op, "statfs")) {
+ Cleanup(gioop_statfs(w, t, g));
+
+ } else if (Eq(t->op, "statfs64")) {
+ Cleanup(gioop_statfs64(w, t, g));
+
+ } else if (Eq(t->op, "fstat")) {
+ Cleanup(gioop_fstat(w, t, g));
+
+ } else if (Eq(t->op, "fstatat")) {
+ Cleanup(gioop_fstatat(w, t, g));
+
+ } else if (Eq(t->op, "fstatfs")) {
+ Cleanup(gioop_fstatfs(w, t, g));
+
+ } else if (Eq(t->op, "fstatfs64")) {
+ Cleanup(gioop_fstatfs64(w, t, g));
+
+ } else if (Eq(t->op, "rename")) {
+ Cleanup(gioop_rename(w, t, g));
+
+ } else if (Eq(t->op, "renameat")) {
+ Cleanup(gioop_renameat(w, t, g));
+
+ } else if (Eq(t->op, "renameat2")) {
+ Cleanup(gioop_renameat2(w, t, g));
+
+ } else if (Eq(t->op, "read")) {
+ Cleanup(gioop_read(w, t, g));
+
+ } else if (Eq(t->op, "readv")) {
+ Cleanup(gioop_readv(w, t, g));
+
+ } else if (Eq(t->op, "readahead")) {
+ Cleanup(gioop_readahead(w, t, g));
+
+ } else if (Eq(t->op, "readdir")) {
+ Cleanup(gioop_readdir(w, t, g));
+
+ } else if (Eq(t->op, "readlink")) {
+ Cleanup(gioop_readlink(w, t, g));
+
+ } else if (Eq(t->op, "readlinkat")) {
+ Cleanup(gioop_readlinkat(w, t, g));
+
+ } else if (Eq(t->op, "write")) {
+ Cleanup(gioop_write(w, t, g));
+
+ } else if (Eq(t->op, "writev")) {
+ Cleanup(gioop_writev(w, t, g));
+
+ } else if (Eq(t->op, "lseek")) {
+ Cleanup(gioop_lseek(w, t, g));
+
+ } else if (Eq(t->op, "getdents")) {
+ Cleanup(gioop_getdents(w, t, g));
+
+ } else if (Eq(t->op, "mkdir")) {
+ Cleanup(gioop_mkdir(w, t, g));
+
+ } else if (Eq(t->op, "rmdir")) {
+ Cleanup(gioop_rmdir(w, t, g));
+
+ } else if (Eq(t->op, "mkdirat")) {
+ Cleanup(gioop_mkdirat(w, t, g));
+
+ } else if (Eq(t->op, "unlink")) {
+ Cleanup(gioop_unlink(w, t, g));
+
+ } else if (Eq(t->op, "unlinkat")) {
+ Cleanup(gioop_unlinkat(w, t, g));
+
+ } else if (Eq(t->op, "lstat")) {
+ Cleanup(gioop_lstat(w, t, g));
+
+ } else if (Eq(t->op, "fsync")) {
+ Cleanup(gioop_fsync(w, t, g));
+
+ } else if (Eq(t->op, "fdatasync")) {
+ Cleanup(gioop_fdatasync(w, t, g));
+
+ } else if (Eq(t->op, "sync")) {
+ Cleanup(gioop_sync(w, t, g));
+
+ } else if (Eq(t->op, "syncfs")) {
+ Cleanup(gioop_syncfs(w, t, g));
+
+ } else if (Eq(t->op, "sync_file_range")) {
+ Cleanup(gioop_sync_file_range(w, t, g));
+
+ } else if (Eq(t->op, "fcntl")) {
+ Cleanup(gioop_fcntl(w, t, g));
+
+ } else if (Eq(t->op, "fcntl")) {
+ Cleanup(gioop_fcntl(w, t, g));
+
+ } else if (Eq(t->op, "mmap2")) {
+ // Support for mmap added later
+
+ } else if (Eq(t->op, "munmap")) {
+ // Support for mmap added later
+
+ } else if (Eq(t->op, "mremap")) {
+ // Support for mmap added later
+
+ } else if (Eq(t->op, "msync")) {
+ // Support for mmap added later
+
+ } else if (Eq(t->op, "chmod")) {
+ Cleanup(gioop_chmod(w, t, g));
+
+ } else if (Eq(t->op, "fchmodat")) {
+ Cleanup(gioop_chmod(w, t, g));
+
+ } else if (Eq(t->op, "fchmod")) {
+ Cleanup(gioop_fchmod(w, t, g));
+
+ } else if (Eq(t->op, "chown")) {
+ Cleanup(gioop_chown(w, t, g));
+
+ } else if (Eq(t->op, "chown16")) {
+ Cleanup(gioop_chown(w, t, g));
+
+ } else if (Eq(t->op, "lchown")) {
+ Cleanup(gioop_lchown(w, t, g));
+
+ } else if (Eq(t->op, "lchown16")) {
+ Cleanup(gioop_lchown(w, t, g));
+
+ } else if (Eq(t->op, "fchown")) {
+ Cleanup(gioop_fchown(w, t, g));
+
+ } else if (Eq(t->op, "fchownat")) {
+ Cleanup(gioop_chown(w, t, g));
+
+ } else if (Eq(t->op, "exit_group")) {
+ Cleanup(gioop_exit_group(w, t, g));
+
+ } else {
+ Cleanup(ERROR;);
+ }
+
+cleanup:
+
+#ifdef LOG_FILTERED
+ if (ret != SUCCESS)
+ t->filtered_where = __FILE__;
+#endif
+
+ t->ret = ret;
+ return ret;
+}
+
+status_e gioop_open(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd || t->path == NULL || t->flags == -1) {
+ return ERROR;
+ }
+
+ gprocess_create_vfd_by_realfd(t->gprocess, t, g);
+ generate_vsize_by_path(g, t, NULL);
+
+ Gioop_write(OPEN, "%ld|%s|%d|%d|open",
+ t->mapped_fd, t->path, t->mode, t->flags);
+
+ if (t->fd > 0)
+ vsize_open(t->vsize, t->vfd, t->path, t->flags);
+
+ return SUCCESS;
+}
+
+status_e gioop_openat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd || t->path == NULL || t->flags == -1) {
+ return ERROR;
+ }
+
+ gprocess_create_vfd_by_realfd(t->gprocess, t, g);
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(OPEN_AT, "%ld|%s|%d|%d|openat",
+ t->mapped_fd,t->path, t->mode, t->flags);
+ if (t->fd > 0)
+ vsize_open(t->vsize, t->vfd, t->path, t->flags);
+
+ return SUCCESS;
+}
+
+status_e gioop_creat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd || t->path == NULL || t->flags == -1) {
+ return ERROR;
+ }
+
+ gprocess_create_vfd_by_realfd(t->gprocess, t, g);
+ generate_vsize_by_path(g, t, NULL);
+
+ Gioop_write(CREAT, "%ld|%s|%d|%d|creat",
+ t->mapped_fd, t->path, t->mode, t->flags);
+ if (t->fd > 0)
+ vsize_open(t->vsize, t->vfd, t->path, t->flags);
+
+ return SUCCESS;
+}
+
+
+status_e gioop_close(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(CLOSE, "%ld|%d|close", t->mapped_fd, t->status);
+
+ if (t->status == 0)
+ vsize_close(t->vsize, t->vfd);
+
+ hmap_remove_l(t->gprocess->fd_map, t->fd);
+ hmap_remove_l(t->gprocess->vfd_map, t->mapped_fd);
+
+ if (!(rbuffer_insert(g->vfd_buffer, t->vfd)))
+ vfd_destroy(t->vfd);
+
+ return SUCCESS;
+}
+
+status_e gioop_stat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(STAT, "%s|%d|stat", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_statfs(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(STATFS, "%s|%d|statfs", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_statfs64(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(STATFS64, "%s|%d|statfs64", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_fstat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FSTAT, "%ld|%d|fstat", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_fstatat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(FSTAT_AT, "%s|%d|fstatat", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_fstatfs(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FSTATFS, "%ld|%d|fstatfs", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_fstatfs64(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FSTATFS64, "%ld|%d|fstatfs64", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_rename(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL || t->path2 == NULL ) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(RENAME, "%s|%s|%d|rename", t->path, t->path2, t->status);
+
+ if (t->status == 0) {
+ t->vsize2 = generate_vsize_by_path(g, NULL, t->path2);
+ vsize_rename(t->vsize, t->vsize2, t->path, t->path2);
+ }
+
+ return SUCCESS;
+}
+
+status_e gioop_renameat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL || t->path2 == NULL ) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(RENAME_AT, "%s|%s|%d|renameat", t->path, t->path2, t->status);
+
+ if (t->status == 0) {
+ t->vsize2 = generate_vsize_by_path(g, NULL, t->path2);
+ vsize_rename(t->vsize, t->vsize2, t->path, t->path2);
+ }
+
+ return SUCCESS;
+}
+status_e gioop_renameat2(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL || t->path2 == NULL ) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(RENAME_AT2, "%s|%s|%d|renameat2",
+ t->path, t->path2, t->status);
+
+ if (t->status == 0) {
+ t->vsize2 = generate_vsize_by_path(g, NULL, t->path2);
+ vsize_rename(t->vsize, t->vsize2, t->path, t->path2);
+ }
+
+ return SUCCESS;
+}
+
+status_e gioop_read(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(READ, "%ld|%ld|read", t->mapped_fd, t->bytes);
+
+ if (t->bytes > 0)
+ vsize_read(t->vsize, t->vfd, t->vfd->path, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_readv(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(READ, "%ld|%ld|readv", t->mapped_fd, t->bytes);
+
+ if (t->bytes > 0)
+ vsize_read(t->vsize, t->vfd, t->vfd->path, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_readahead(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(READAHEAD, "%ld|%ld|%ld|readahead",
+ t->mapped_fd, t->offset, t->count);
+
+ return SUCCESS;
+}
+
+status_e gioop_readdir(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(READDIR, "%ld|%d|readdir", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_readlink(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(READLINK, "%s|%d|readlink", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_readlinkat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(READLINK_AT, "%s|%d|readlinkat", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_write(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(WRITE, "%ld|%ld|write", t->mapped_fd, t->bytes);
+
+ if (t->bytes > 0)
+ vsize_write(t->vsize, t->vfd, t->path, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_writev(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(WRITEV, "%ld|%ld|writev", t->mapped_fd, t->bytes);
+
+ if (t->bytes > 0)
+ vsize_write(t->vsize, t->vfd, t->path, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_lseek(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(LSEEK, "%ld|%ld|%ld|%ld|lseek",
+ t->mapped_fd, t->offset, t->whence, t->bytes);
+
+ if (t->bytes >= 0)
+ vsize_seek(t->vsize, t->vfd, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_getdents(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(GETDENTS, "%ld|%ld|%ld|getdents",
+ t->mapped_fd, t->count, t->bytes);
+
+ return SUCCESS;
+}
+
+status_e gioop_mkdir(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(MKDIR, "%s|%d|%d|mkdir", t->path, t->mode, t->status);
+
+ if (t->status == 0)
+ vsize_mkdir(t->vsize, t->path);
+
+ return SUCCESS;
+}
+status_e gioop_rmdir(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(MKDIR, "%s|%d|rmdir", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_rmdir(t->vsize, t->path);
+
+ return SUCCESS;
+}
+status_e gioop_mkdirat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(MKDIR_AT, "%s|%d|%d|mkdirat", t->path, t->mode, t->status);
+
+ if (t->status == 0)
+ vsize_mkdir(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_unlink(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(UNLINK, "%s|%d|unlink", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_unlink(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_unlinkat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(UNLINK_AT, "%s|%d|unlinkat", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_unlink(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_lstat(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(LSTAT, "%s|%d|lstat", t->path, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_fsync(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FSYNC, "%ld|%d|fsync", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_fdatasync(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FDATASYNC, "%ld|%d|fdatasync", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_sync(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ Gioop_write(SYNC, "%d|sync", t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_syncfs(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(SYNCFS, "%ld|%d|syncfs", t->mapped_fd, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_sync_file_range(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(SYNC_FILE_RANGE, "%ld|%ld|%ld|%d|sync_file_range",
+ t->mapped_fd, t->offset, t->bytes, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_fcntl(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ switch (t->F) {
+ case F_GETFD:
+ case F_GETFL:
+ case F_SETFD:
+ case F_SETFL:
+ break;
+ default:
+ return ERROR;
+ break;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FCNTL, "%ld|%d|%d|%d|fcntl",
+ t->mapped_fd, t->F, t->G, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_chmod(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ Gioop_write(CHMOD, "%s|%d|%d|chmod", t->path, t->mode, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_fchmod(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(FCHMOD, "%ld|%d|%d|fchmod", t->mapped_fd, t->mode, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_chown(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ // Hmm, maybe rename t->offset, because here it is used for the user UID
+ Gioop_write(CHOWN, "%s|%ld|%d|%d|chown", t->path, t->offset, t->G, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_fchown(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (!t->has_fd) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ // Hmm, maybe rename t->offset, because here it is used for the user UID
+ Gioop_write(FCHOWN, "%ld|%ld|%d|%d|fchown", t->mapped_fd, t->offset, t->G, t->status);
+
+ return SUCCESS;
+}
+
+status_e gioop_lchown(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ if (t->path == NULL) {
+ return ERROR;
+ }
+
+ generate_vsize_by_path(g, t, NULL);
+ // Hmm, maybe rename t->offset, because here it is used for the user UID
+ Gioop_write(LCHOWN, "%s|%ld|%d|%d|chown", t->path, t->offset, t->G, t->status);
+
+ if (t->status == 0)
+ vsize_stat(t->vsize, t->path);
+
+ return SUCCESS;
+}
+
+status_e gioop_exit_group(gwriter_s *w, gtask_s *t, generate_s *g)
+{
+ // It means that the process and all its threads terminate.
+ // Therefore close all file handles of that process!
+ hmap_run_cb2(t->gprocess->vfd_map, gioop_close_all_vfd_cb, t);
+
+ // Remove virtual process from pid map and destroy it
+ amap_unset(g->pid_map, t->pid);
+ gprocess_destroy(t->gprocess);
+
+ return SUCCESS;
+}
+
+void gioop_close_all_vfd_cb(void *data, void *data2)
+{
+ gtask_s *t = data2;
+ t->vfd = data;
+ generate_s *g = t->generate;
+
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(CLOSE, "%ld|%d|close on exit_group", t->vfd->mapped_fd, 0);
+ vsize_close(t->vsize, t->vfd);
+}
+
diff --git a/ioreplay/src/generate/gioop.h b/ioreplay/src/generate/gioop.h
new file mode 100644
index 0000000..ad49713
--- /dev/null
+++ b/ioreplay/src/generate/gioop.h
@@ -0,0 +1,102 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GIOOP_H
+#define GIOOP_H
+
+#include "../defaults.h"
+#include "gwriter.h"
+#include "gtask.h"
+#include "generate.h"
+
+
+// Helper macro regarding writing the .replay file!
+
+#define Gioop_write(op, ...) \
+ fprintf(g->replay_fd, "%ld|%ld|%ld|0|0|%d|", \
+ t->mapped_time, \
+ (t->vsize ? t->vsize->id : 0),\
+ t->gprocess->mapped_pid, \
+ op); \
+ fprintf(g->replay_fd, __VA_ARGS__); \
+ fprintf(g->replay_fd, "@%ld", t->lineno); \
+ fprintf(g->replay_fd, "|\n")
+
+/**
+ * @brief Function used when closing all virtual FDs of a virtual process
+ *
+ * This function is run on all virtual file handles whenever a virtual generate
+ * process object (gprocess_s) gets destroyed. This is on an exit_group
+ * syscall (a thread group, a process with all its threads, terminates). Upon
+ * process termination Linux also closes all its file descriptors! This is what
+ * we simulate here!
+ *
+ * @param data The pointer to the virtual file descriptor object
+ * @param data2 The pointer to the corresponding generate task object.
+ */
+void gioop_close_all_vfd_cb(void *data, void *data2);
+
+/**
+ * @brief Run a generate I/O operation on a given task
+ *
+ * @param w The writer object
+ * @param t The task object
+ * @return SUCCESS if everything went fine
+ */
+status_e gioop_run(gwriter_s *w, gtask_s *t);
+
+status_e gioop_open(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_openat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_creat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_close(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_stat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_statfs(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_statfs64(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fstat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fstatat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fstatfs(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fstatfs64(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_rename(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_renameat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_renameat2(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_read(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_readv(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_readahead(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_readdir(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_readlink(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_readlinkat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_write(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_writev(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_lseek(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_getdents(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_mkdir(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_rmdir(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_mkdirat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_unlink(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_unlinkat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_lstat(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fsync(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fdatasync(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_sync(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_syncfs(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_sync_file_range(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fcntl(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_chmod(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fchmod(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_chown(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_fchown(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_lchown(gwriter_s *w, gtask_s *t, generate_s *g);
+status_e gioop_exit_group(gwriter_s *w, gtask_s *t, generate_s *g);
+
+#endif // GIOOP_H
diff --git a/ioreplay/src/generate/gparser.c b/ioreplay/src/generate/gparser.c
new file mode 100644
index 0000000..514128f
--- /dev/null
+++ b/ioreplay/src/generate/gparser.c
@@ -0,0 +1,356 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gparser.h"
+
+#include "gtask.h"
+#include "gwriter.h"
+
+void* gparser_pthread_start(void *data)
+{
+ gparser_s *p = data;
+ generate_s *g = p->generate;
+ gwriter_s *w = g->writer;
+ gtask_s *t = NULL;
+
+ do {
+ while (NULL != (t = rbuffer_get_next(p->queue))) {
+ // First extract
+ gparser_extract(p, t);
+ // Second, pass the task to the writer thread
+ rbuffer_insert(w->queue, t);
+ }
+ usleep(100);
+ } while (!p->terminate);
+
+ while (NULL != (t = rbuffer_get_next(p->queue))) {
+ gparser_extract(p, t);
+ rbuffer_insert(w->queue, t);
+ }
+
+ return NULL;
+}
+
+gparser_s* gparser_new(generate_s *g)
+{
+ gparser_s *p = Malloc(gparser_s);
+
+ p->generate = g;
+ p->terminate = false;
+ p->queue = rbuffer_new(1024);
+
+ return p;
+}
+
+void gparser_start(gparser_s *p)
+{
+ start_pthread(&p->pthread, gparser_pthread_start, (void*)p);
+}
+
+void gparser_destroy(gparser_s *p)
+{
+ rbuffer_destroy(p->queue);
+ free(p);
+}
+
+void gparser_terminate(gparser_s *p)
+{
+ p->terminate = true;
+ pthread_join(p->pthread, NULL);
+}
+
+void gparser_extract(gparser_s *p, gtask_s *t)
+{
+ status_e ret = SUCCESS;
+ generate_s *g = p->generate;
+
+ char *saveptr;
+ char* tok = strtok2_r(t->line, ";:,", &saveptr);
+ int ntoks = 0;
+
+ while (tok) {
+ if (++ntoks > MAX_TOKENS) {
+ ret = ERROR;
+ break;
+ }
+ ret = gparser_extract_tok(p, t, tok);
+ if (ret != SUCCESS)
+ break;
+
+ tok = strtok2_r(NULL, ";:,", &saveptr);
+ }
+
+ if (ret == SUCCESS) {
+
+ // Check for the existance of mandatory values!
+ if (t->pid < 0 || t->tid < 0) {
+ Cleanup(ERROR);
+
+ } else if (t->op == NULL) {
+ Cleanup(ERROR);
+
+ } else if (t->mapped_time == -1) {
+ Cleanup(ERROR);
+ }
+
+ // We are inserting ".ioreplay/NAME" to the paths. This enables us to
+ // run multiple tests simoultaneously.
+
+ if (t->path) {
+ if (!mounts_transform_path(g->mps, g->name,
+ t->path, &t->path_r)) {
+ Cleanup(ERROR);
+ }
+ if (t->path_r)
+ t->path = t->path_r;
+ }
+
+ if (t->path2) {
+ if (!mounts_transform_path(g->mps, g->name,
+ t->path2, &t->path2_r)) {
+ Cleanup(ERROR);
+ }
+ if (t->path2_r)
+ t->path2 = t->path2_r;
+ }
+
+ }
+
+cleanup:
+
+ t->ret = ret;
+
+#ifdef LOG_FILTERED
+ t->filtered_where = __FILE__;
+#endif
+}
+
+status_e gparser_extract_tok(gparser_s *p, gtask_s *t, char *tok)
+{
+ status_e ret = SUCCESS;
+
+ if (gparser_token_not_ok(p, tok)) {
+ Cleanup(ERROR);
+ }
+
+ generate_s *g = t->generate;
+
+ char key = tok[0];
+ char *value = tok;
+ value += 2;
+
+ switch (key) {
+ case 'a':
+ // Address
+ t->address = strtol(value, NULL, 10);
+ break;
+
+ case 'A':
+ // Address 2
+ t->address2 = strtol(value, NULL, 10);
+ break;
+
+ case 'b':
+ // Bytes
+ if (t->bytes != -1) {
+ Cleanup(ERROR);
+ }
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->bytes = strtol(value, NULL, 10);
+ break;
+
+ case 'c':
+ // Count
+ if (t->count != -1) {
+ Cleanup(ERROR);
+ }
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->count = strtol(value, NULL, 10);
+ break;
+
+ case 'd':
+ // Descriptor
+ if (t->fd != -1) {
+ Cleanup(ERROR);
+ }
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->fd = atoi(value);
+ if (t->fd > 0)
+ t->has_fd = true;
+ break;
+
+ case 'f':
+ // Flags
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->flags = atoi(value);
+ break;
+
+ case 'i':
+ // PID:TID
+ t->pidtid = value;
+ // Extract PID and TID from "PID:TID"
+ if (!gparser_get_pidtid(p, t->pidtid, &t->pid, &t->tid)) {
+ Cleanup(ERROR);
+ }
+ break;
+
+ case 'm':
+ // Mode
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->mode = atoi(value);
+ break;
+
+ case 'o':
+ // Operation
+ t->op = value;
+ break;
+
+ case 'O':
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->offset = strtol(value, NULL, 10);
+ break;
+
+ case 'W':
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->whence = strtol(value, NULL, 10);
+ break;
+
+ case 'p':
+ // File path
+ t->path = value;
+ chreplace(t->path, '|', '_');
+ strunquote(t->path);
+ break;
+
+ case 'P':
+ // File path 2
+ t->path2 = value;
+ chreplace(t->path2, '|', '_');
+ strunquote(t->path2);
+ break;
+
+ case 's':
+ // Cleanup status
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->status = atoi(value);
+ break;
+
+ case 't':
+ // Time
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->mapped_time = strtol(value, NULL, 10);
+ // Start replay time from 0
+ if (g->start_time == -1) {
+ g->start_time = t->mapped_time;
+ }
+ t->mapped_time -= g->start_time;
+ break;
+
+ case 'F':
+ // FCNTL function
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->F = atoi(value);
+ break;
+
+ case 'G':
+ // FCNTL argument
+ if (is_number(value) == 0) {
+ Cleanup(ERROR);
+ }
+ t->G = atoi(value);
+ break;
+
+ case 'T':
+ break;
+
+ default:
+ // Unknown key
+ {
+ Cleanup(ERROR);
+ }
+ }
+
+cleanup:
+ if (t->path_r) {
+ free(t->path_r);
+ t->path_r = NULL;
+ }
+ if (t->path2_r) {
+ free(t->path2_r);
+ t->path2_r = NULL;
+ }
+
+ return ret;
+}
+
+bool gparser_token_not_ok(gparser_s *p, char *tok)
+{
+ if (strlen(tok) < 3) {
+ return true;
+
+ } else if (tok[1] != '=') {
+ return true;
+ }
+
+ return false;
+}
+
+bool gparser_get_pidtid(gparser_s *p, char *pidtid, long *pid, long *tid)
+{
+ char *pos = strchr(pidtid, ':');
+
+ if (pos) {
+ char *tmp = pos;
+ tmp++;
+
+ if (is_number(tmp)) {
+ *tid = atol(tmp);
+ } else {
+ return false;
+ }
+
+ pos[0] = '\0';
+ if (is_number(pidtid)) {
+ *pid = atol(pidtid);
+ } else {
+ return false;
+ }
+ }
+
+ else {
+ return false;
+ }
+
+ return (*pid >= 0 && *tid >= 0);
+}
diff --git a/ioreplay/src/generate/gparser.h b/ioreplay/src/generate/gparser.h
new file mode 100644
index 0000000..f3e204a
--- /dev/null
+++ b/ioreplay/src/generate/gparser.h
@@ -0,0 +1,113 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GPARSER_H
+#define GPARSER_H
+
+#include "../datas/rbuffer.h"
+#include "../defaults.h"
+#include "generate.h"
+#include "gtask.h"
+
+/**
+ * @brief The parser definition
+ *
+ * The parser is to extract all information from the .capture file.
+ */
+typedef struct gparser_s_ {
+ bool terminate; /**< The parser thread will terminate if set to true */
+ generate_s *generate; /**< The generate object */
+ pthread_t pthread; /**< The posix thread */
+ rbuffer_s *queue; /**< A queue of task objects */
+} gparser_s;
+
+/**
+ * @brief Creates a new parser
+ *
+ * @param g The generate object
+ * @return The new parser object
+ */
+gparser_s* gparser_new(generate_s *g);
+
+/**
+ * @brief Starts the parser thread
+ *
+ * @param p The parser object
+ */
+void gparser_start(gparser_s *p);
+
+/**
+ * @brief Terminates the parser thread
+ *
+ * @param p The parser object
+ */
+void gparser_terminate(gparser_s *p);
+
+/**
+ * @brief Destroys the parser thread
+ *
+ * @param p The parser object
+ */
+void gparser_destroy(gparser_s *p);
+
+/**
+ * @brief Extracts information a .capture line
+ *
+ * Extracts information from a .capture line and stores it into the task
+ * object.
+ *
+ * @param p The parser object
+ * @param t The task object
+ */
+void gparser_extract(gparser_s *p, gtask_s *t);
+
+/**
+ * @brief Extracts information from a specific token string
+ *
+ * @param p The parser object
+ * @param t The task object
+ * @param tok The token string
+ * @return Returns with SUCCESS on success
+ */
+status_e gparser_extract_tok(gparser_s *p, gtask_s *t, char *tok);
+
+/**
+ * @brief Verifies the correctness of a token
+ *
+ * @param p The parser object
+ * @param tok The token to be verified
+ * @return true if token verified successfully
+ */
+bool gparser_token_not_ok(gparser_s *p, char *tok);
+
+/**
+ * @brief Checks whether the pidtid string is correct or not
+ *
+ * @param p The parser object
+ * @param pidtid The string to check
+ * @param pid The pointer to the resulting pid
+ * @param tid The pointer to the resulting tid
+ * @return true on success
+ */
+bool gparser_get_pidtid(gparser_s *p, char *pidtid, long *pid, long *tid);
+
+/**
+ * @brief Entry point of the parser POSIX thread
+ *
+ * @param data A pointer to the parser object
+ * return Always NULL
+ */
+void* gparser_pthread_start(void *data);
+
+#endif // GPARSER_H
diff --git a/ioreplay/src/generate/gprocess.c b/ioreplay/src/generate/gprocess.c
new file mode 100644
index 0000000..6a0b37a
--- /dev/null
+++ b/ioreplay/src/generate/gprocess.c
@@ -0,0 +1,101 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gprocess.h"
+
+#include "../vfd.h"
+#include "gioop.h"
+
+void _gprocess_vfd_map_destroy_cb(void *data)
+{
+ vfd_destroy(data);
+}
+
+gprocess_s* gprocess_new(const long pid, const long mapped_pid)
+{
+ gprocess_s* gp = Malloc(gprocess_s);
+
+ gp->pid = pid;
+ gp->mapped_pid = mapped_pid;
+ gp->max_mapped_fd = 0;
+ gp->fd_map = hmap_new_l(1024);
+ gp->vfd_map = hmap_new_l(1024);
+ gp->vfd_map->data_destroy = _gprocess_vfd_map_destroy_cb;
+
+ return gp;
+}
+
+void gprocess_destroy(gprocess_s *gp)
+{
+ hmap_destroy(gp->vfd_map);
+ hmap_destroy(gp->fd_map);
+ free(gp);
+}
+
+void gprocess_create_vfd_by_realfd(gprocess_s *gp, gtask_s *t, generate_s *g)
+{
+ if (t->fd < 0)
+ return;
+
+ // Check whether the real FD is still open according to the .capture log
+ long old_mapped = (long) hmap_get_l(gp->fd_map, t->fd);
+ if (old_mapped) {
+
+ // That real file descriptor is already with a mapping to a virtual
+ // file descriptor. This may happen when SystemTap missed to trace a
+ // 'close' syscall. We are inserting a close now...
+
+ t->vfd = hmap_get_l(gp->vfd_map, old_mapped);
+
+ hmap_remove_l(gp->fd_map, t->fd);
+ hmap_remove_l(gp->vfd_map, old_mapped);
+
+ if (t->vfd) {
+ generate_vsize_by_path(g, t, t->vfd->path);
+ Gioop_write(CLOSE, "%ld|%d|close inserted", old_mapped, 0);
+ vsize_close(t->vsize, t->vfd);
+ if (!(rbuffer_insert(g->vfd_buffer, t->vfd)))
+ vfd_destroy(t->vfd);
+ }
+ }
+
+ t->vfd = rbuffer_get_next(g->vfd_buffer);
+ t->mapped_fd = ++g->num_mapped_fds;
+ if (!t->vfd)
+ t->vfd = vfd_new(t->fd, t->mapped_fd, t->path);
+ else
+ vfd_update(t->vfd, t->fd, t->mapped_fd, t->path);
+ t->vfd->free_path = t->path_r != NULL;
+
+ hmap_insert_l(gp->vfd_map, t->mapped_fd, t->vfd);
+ hmap_insert_l(gp->fd_map, t->fd, (void*)t->mapped_fd);
+}
+
+status_e gprocess_vfd_by_realfd(gprocess_s *gp, gtask_s *t)
+{
+ t->mapped_fd = (long) hmap_get_l(gp->fd_map, t->fd);
+ if (t->mapped_fd == 0) {
+ // No corresponding virtual fd number mapping
+ t->has_fd = false;
+
+ } else {
+ t->vfd = hmap_get_l(gp->vfd_map, t->mapped_fd);
+ if (!t->vfd) {
+ return ERROR;
+ }
+ t->mapped_fd = t->vfd->mapped_fd;
+ }
+
+ return SUCCESS;
+}
diff --git a/ioreplay/src/generate/gprocess.h b/ioreplay/src/generate/gprocess.h
new file mode 100644
index 0000000..47e5037
--- /dev/null
+++ b/ioreplay/src/generate/gprocess.h
@@ -0,0 +1,90 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GPROCESS_H
+#define GPROCESS_H
+
+#include "../datas/hmap.h"
+#include "../defaults.h"
+#include "gtask.h"
+#include "generate.h"
+
+// Forward declarations (header include hell)
+struct gtask_s_;
+struct generate_s_;
+
+/**
+ * @brief Virtual process object used for generating .replay file
+ *
+ * An object of this represents a Linux process in generate context.
+ * In Linux every process owns * its own file descriptor table which is
+ * simulated here. Usually, a Linux process re-uses a FD number once not used
+ * anymore (e.g. after a close). However, as we want to increase concurrency
+ * while replaying the I/O we want * to ensure to always use unique file
+ * descriptor IDs for every open. Thats why we use max_mapped_fd to always
+ * map a real FD number to a uniq virtual FD number.
+ */
+typedef struct gprocess_s_ {
+ long pid; /**< The real PID */
+ long mapped_pid; /**< The mapped PID */
+ hmap_s *vfd_map; /**< All virtual file descriptors of that process */
+ hmap_s *fd_map; /**< All mappings from real fd to virtual fd */
+ long max_mapped_fd; /**< The max mapped fd number */
+} gprocess_s;
+
+/**
+ * @brief Creates a new gprocess object
+ *
+ * @param pid The process ID
+ * @param mapped_pid the mapped PID
+ * @return The new gprocess object
+ */
+gprocess_s* gprocess_new(const long pid, const long mapped_pid);
+
+/**
+ * @brief Destroys a gprocess object
+ *
+ * @param gp The gprocess object
+ */
+void gprocess_destroy(gprocess_s *gp);
+
+/**
+ * @brief Creates a new virtual FD from a given real FD number
+ *
+ * In ioreplay we map the real file descriptor (the fd number protocolled in
+ * the.capture file) to a virtual file descriptor (the fd numner written to the
+ * .replay file). The purpose is to increase concurrency of the I/O during
+ * replay. Normally, a process would reuse the same file descriptor number
+ * once closed earlier. However, when replaying we can't reuse the number if
+ * we want to replay the I/O on multiple paths in parallel. Therefore, it is
+ * ensured that the virtual file descriptor number in the .replay file is
+ * always * unique for every open!
+ *
+ * @param gp The process object
+ * @param t The task object (the vfd pointer will be stored to * t->vfd)
+ * @param g The generate object
+ */
+void gprocess_create_vfd_by_realfd(gprocess_s *gp, struct gtask_s_ *t,
+ struct generate_s_ *g);
+
+/**
+ * @brief Retrieves a virtual FD from a given real FD number
+ *
+ * @param gp The process object
+ * @param t The task object (the vfd pointer will be stored to * t->vfd)
+ * @return SUCCESS if everything went smothly!
+ */
+status_e gprocess_vfd_by_realfd(gprocess_s *gp, struct gtask_s_ *t);
+
+#endif // GPROCESS_H
diff --git a/ioreplay/src/generate/gtask.c b/ioreplay/src/generate/gtask.c
new file mode 100644
index 0000000..55a1124
--- /dev/null
+++ b/ioreplay/src/generate/gtask.c
@@ -0,0 +1,91 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtask.h"
+
+gtask_s* gtask_new(void *generate)
+{
+ gtask_s *t = Malloc(gtask_s);
+
+ t->generate = generate;
+ t->line = NULL;
+ t->path_r = NULL;
+ t->path2_r = NULL;
+#ifdef LOG_FILTERED
+ t->original_line = NULL;
+#endif
+
+ return t;
+}
+
+void gtask_init(gtask_s *t, char *line, const unsigned long lineno)
+{
+ if (t->line)
+ free(t->line);
+ t->line = Clone(line);
+
+ if (t->path_r)
+ free(t->path_r);
+ if (t->path2_r)
+ free(t->path2_r);
+
+#ifdef LOG_FILTERED
+ if (t->original_line)
+ free(t->original_line);
+ t->original_line = Clone(line);
+ t->filtered_where = NULL;
+#endif
+
+ t->bytes = -1;
+ t->address = 0;
+ t->address2 = 0;
+ t->count = -1;
+ t->F = -1;
+ t->fd = -1;
+ t->flags = -1;
+ t->G = -1;
+ t->has_fd = false;
+ t->vsize = NULL;
+ t->vsize2 = NULL;
+ t->lineno = lineno;
+ t->mapped_fd = -1;
+ t->mapped_time = -1;
+ t->mode = -1;
+ t->offset = -1;
+ t->op = NULL;
+ t->path2 = NULL;
+ t->path2_r = NULL;
+ t->path = NULL;
+ t->path_r = NULL;
+ t->gprocess = NULL;
+ t->pid = -1;
+ t->pidtid = NULL;
+ t->ret = 0;
+ t->status = -1;
+ t->tid = -1;
+ t->vfd = NULL;
+ t->whence = -1;
+}
+
+void gtask_destroy(gtask_s *t)
+{
+ if (t->line)
+ free(t->line);
+ if (t->path_r)
+ free(t->path_r);
+ if (t->path2_r)
+ free(t->path2_r);
+ free(t);
+}
+
diff --git a/ioreplay/src/generate/gtask.h b/ioreplay/src/generate/gtask.h
new file mode 100644
index 0000000..2f364d3
--- /dev/null
+++ b/ioreplay/src/generate/gtask.h
@@ -0,0 +1,100 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GTASK_H
+#define GTASK_H
+
+#include "vsize.h"
+
+#include "gprocess.h"
+#include "../vfd.h"
+#include "../datas/amap.h"
+#include "../datas/hmap.h"
+#include "../datas/rbuffer.h"
+#include "../defaults.h"
+#include "../mounts.h"
+#include "../options.h"
+
+/**
+ * @brief The generate task definition
+ *
+ * The gtask holds all possible variables required to process a particular
+ * .capture line and to generate the corresponding .replay line
+ */
+typedef struct gtask_s_ {
+ bool has_fd; /**< True if task has a file descriptor number */
+ char *line; /**< A pointer to the remaining part of the .capture line */
+ char *op; /**< Operation/syscall name */
+ char *path2; /**< A second path name (e.g. for rename) */
+ char *path2_r; /**< Work around to track mallocs, so it can be freed */
+ char *path; /**< Path name */
+ char *path_r; /**< Work around to track mallocs, so it can be freed */
+ char *pidtid; /**< String representing pid:tid */
+ int F; /**< Arguments for fcntl syscall */
+ int G; /**< Arguments for fcntl syscall */
+ int fd; /**< File descriptor number */
+ int flags; /**< File open flags */
+ int mode; /**< File open mode */
+ int ret; /**< ioreplay process status, SUCCESS if everything is alright */
+ int status; /**< Operation/syscall return status */
+ long address2; /**< Another address (used by mmap related syscalls) */
+ long address; /**< An address (used by mmap related syscalls) */
+ long bytes; /**< Amount of bytes */
+ long count; /**< A count */
+ long lineno; /**< The current line number */
+ long mapped_fd; /**< The mapped file descriptor number */
+ long mapped_time; /**< The mapped time */
+ long offset; /**< A offset */
+ long pid; /**< The process ID */
+ long tid; /**< The thread ID */
+ long whence; /**< Whence */
+ vfd_s *vfd; /**< A pointer to the virtual file descriptor */
+ struct gprocess_s_ *gprocess; /**< A pointer to the process object */
+ void *generate; /**< A pointer to the generate object */
+ vsize_s *vsize2; /**< Pointer to a second virtual size object */
+ vsize_s *vsize; /**< Pointer to the virtual size object */
+#ifdef LOG_FILTERED
+ char *original_line; /**< Only used for debugging purposes */
+ char *filtered_where; /**< Only used for debugging purposes */
+#endif
+} gtask_s;
+
+/**
+ * @brief Creates a new task object
+ *
+ * @param generate A pointer to the generate object
+ * @return The new task object
+ */
+gtask_s* gtask_new(void *generate);
+
+/**
+ * @brief Initialises a taks object
+ *
+ * This function is used in particular when we recycle/reuse an old
+ * gtask object.
+ *
+ * @param t The gtask object
+ * @param line The corresponding line from the .capture file
+ * @param lineno The line number
+ */
+void gtask_init(gtask_s *t, char *line, const unsigned long lineno);
+
+/**
+ * @brief Destroys a given task object
+ *
+ * @param t The task object
+ */
+void gtask_destroy(gtask_s *t);
+
+#endif // GTASK_H
diff --git a/ioreplay/src/generate/gwriter.c b/ioreplay/src/generate/gwriter.c
new file mode 100644
index 0000000..e0d448e
--- /dev/null
+++ b/ioreplay/src/generate/gwriter.c
@@ -0,0 +1,85 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gwriter.h"
+
+#include "gtask.h"
+#include "generate.h"
+#include "gioop.h"
+#include "../opcodes.h"
+
+void* gwriter_pthread_start(void *data)
+{
+ gwriter_s *w = data;
+ generate_s *g = w->generate;
+ gtask_s *t = NULL;
+
+ do {
+ while (NULL != (t = rbuffer_get_next(w->queue))) {
+#ifdef LOG_FILTERED
+ // Logging filtered lines
+ if (SUCCESS != gioop_run(w, t)) {
+ fprintf(g->replay_fd, "#FILTERED @%ld %s", t->lineno,
+ t->original_line);
+ }
+#else
+ gioop_run(w, t);
+#endif
+ rbuffer_insert(g->reuse_queue, t);
+ }
+ usleep(100);
+ } while (!w->terminate);
+
+ while (NULL != (t = rbuffer_get_next(w->queue))) {
+#ifdef LOG_FILTERED
+ if (SUCCESS != gioop_run(w, t)) {
+ fprintf(g->replay_fd, "#FILTERED @%ld %s\n", t->lineno,
+ t->original_line);
+ }
+#else
+ gioop_run(w, t);
+#endif
+ rbuffer_insert(g->reuse_queue, t);
+ }
+
+ return NULL;
+}
+
+gwriter_s* gwriter_new(generate_s *g)
+{
+ gwriter_s *w = Malloc(gwriter_s);
+
+ w->generate = g;
+ w->terminate = false;
+ w->queue = rbuffer_new(1024);
+
+ return w;
+}
+
+void gwriter_start(gwriter_s *w)
+{
+ start_pthread(&w->pthread, gwriter_pthread_start, (void*)w);
+}
+
+void gwriter_destroy(gwriter_s *w)
+{
+ rbuffer_destroy(w->queue);
+ free(w);
+}
+
+void gwriter_terminate(gwriter_s *w)
+{
+ w->terminate = true;
+ pthread_join(w->pthread, NULL);
+}
diff --git a/ioreplay/src/generate/gwriter.h b/ioreplay/src/generate/gwriter.h
new file mode 100644
index 0000000..4295580
--- /dev/null
+++ b/ioreplay/src/generate/gwriter.h
@@ -0,0 +1,86 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GWRITER_H
+#define GWRITER_H
+
+#include "../datas/rbuffer.h"
+#include "../defaults.h"
+#include "vsize.h"
+#include "gtask.h"
+#include "generate.h"
+
+// Forward declaration (header include hell)
+struct gtask_s_;
+struct generate_s_;
+
+/**
+ * @brief Definition of the writer object
+ *
+ * The writer utilises the information extracted by the parser to actually
+ * write the .replay file.
+ */
+typedef struct gwriter_s_ {
+ bool terminate; /**< The writer thread will terminate if set to true */
+ struct generate_s_ *generate; /**< The generate object */
+ pthread_t pthread; /**< The posix thread */
+ rbuffer_s *queue; /**< A queue of task objects */
+} gwriter_s;
+
+/**
+ * @brief Creates a new writer
+ *
+ * @param g The generate object
+ * @return The new writer object
+ */
+gwriter_s* gwriter_new(struct generate_s_ *g);
+
+/**
+ * @brief Starts the writer thread
+ *
+ * @param w The writer object
+ */
+void gwriter_start(gwriter_s *w);
+
+/**
+ * @brief Terminates the writer thread
+ *
+ * @param w The writer object
+ */
+void gwriter_terminate(gwriter_s *w);
+
+/**
+ * @brief Destroys the writer thread
+ *
+ * @param w The writer object
+ */
+void gwriter_destroy(gwriter_s *w);
+
+/**
+ * @brief Writes a line to the .replay file
+ *
+ * @param w The writer object
+ * @param t The task object
+ */
+void gwriter_write(gwriter_s *w, struct gtask_s_ *t);
+
+/**
+ * @brief Entry function of the writer pthread
+ *
+ * @param data A pointer to the writer object
+ * @return Always returns a NULL pointer if it doesnt crash!
+ */
+void* gwriter_pthread_start(void *data);
+
+#endif // GWRITER_H
diff --git a/ioreplay/src/generate/vsize.c b/ioreplay/src/generate/vsize.c
new file mode 100644
index 0000000..f2d56ba
--- /dev/null
+++ b/ioreplay/src/generate/vsize.c
@@ -0,0 +1,247 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "vsize.h"
+
+#include "generate.h"
+
+// Helper macros
+
+#define _Set_file(v) v->is_file = true; v->unsure = v->is_dir = false
+#define _Set_dir(v) v->is_dir = true; v->unsure = v->is_file = false
+#define _Set_unsure(v) v->unsure = true
+#define _Set_inserted(v) v->inserted = true
+#define _Set_renamed(v) v->renamed = true
+#define _Set_required(v) v->required = true
+
+vsize_s* vsize_new(char *file_path, const unsigned long id,
+ void *generate)
+{
+ vsize_s *v = Malloc(vsize_s);
+
+ v->generate = generate;
+ v->id = id;
+ v->inserted = false;
+ v->is_dir = false;
+ v->is_file = false;
+ v->offset = -1;
+ v->path = Clone(file_path);
+ v->renamed = false;
+ v->required = false;
+ v->unsure = false;
+ v->updates = 0;
+ v->vsize = 0;
+ v->vsize_deficit = 0;
+
+ return v;
+}
+
+void vsize_destroy(vsize_s *v)
+{
+ if (!v)
+ return;
+
+ free(v->path);
+ free(v);
+}
+
+void init_parent_dir(vsize_s *v, const char *path)
+{
+ generate_s *g = v->generate;
+ char *clone = Clone(path);
+ char *parent = dirname(clone);
+
+ vsize_s *v_parent = hmap_get(g->vsize_map, parent);
+ if (!v_parent) {
+
+ // Parent directory does not yet have a vsize!
+ // Create a vsize object for it and set it as a pre-requirement
+ // so that the directory can be created during init mode.
+
+ v_parent = vsize_new(parent, ++g->num_vsizes, g);
+ hmap_insert(g->vsize_map, parent, v_parent);
+
+ _Set_required(v_parent);
+ _Set_dir(v_parent);
+
+ // This is for debugging purposes only
+ _Set_inserted(v_parent);
+ v_parent->updates++;
+
+ } else if (v_parent->unsure) {
+ // We now know for sure that this path must be a directory!
+ _Set_dir(v_parent);
+ v_parent->updates++;
+ }
+
+ free(clone);
+}
+
+void vsize_open(vsize_s *v, void *vfd, const char *path, const int flags)
+{
+
+ // v->first_encounter == false means, that this is the first occurance of
+ // this path and we didn't initialise it (means we didn't ensure that
+ // we want to create all parent directories etc.
+
+ if (v->updates == 0) {
+ // We may use a recycled vfd object! When opening a file we always
+ // assume that the offset is 0!
+ vfd_s *vfd_ = vfd;
+ vfd_->offset = 0;
+ init_parent_dir(v, path);
+
+ if (Has(flags, O_DIRECTORY)) {
+ _Set_required(v);
+ _Set_dir(v);
+
+ } else if (Hasnt(flags, O_CREAT)) {
+ _Set_required(v);
+ _Set_file(v);
+ _Set_unsure(v);
+ }
+ v->updates++;
+
+ } else if (v->unsure) {
+ if (Has(flags, O_DIRECTORY)) {
+ // Now we know for sure that this path must be a directory!
+ _Set_dir(v);
+ v->updates++;
+ }
+ }
+}
+
+void vsize_close(vsize_s *v, void* vfd)
+{
+ vfd_s *vfd_ = vfd;
+ vfd_->offset = 0;
+ v->updates++;
+}
+
+void vsize_stat(vsize_s *v, const char *path)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ _Set_required(v);
+ _Set_file(v);
+
+ // We are not 100% sure that this is really a file,
+ // the path might be still a directory though!
+ _Set_unsure(v);
+ v->updates++;
+ }
+}
+
+void vsize_rename(vsize_s *v, vsize_s *v2,
+ const char *path, const char *path2)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ _Set_required(v);
+ _Set_file(v);
+ _Set_unsure(v);
+ v->updates++;
+ }
+
+ if (v2->updates == 0) {
+ init_parent_dir(v2, path2);
+ _Set_file(v2);
+
+ // We are not 100% sure that this is really a file,
+ // the path might be still a directory though!
+ _Set_unsure(v2);
+
+ // For debugging purposes only
+ _Set_renamed(v2);
+ v2->updates++;
+ }
+}
+
+void vsize_adjust(vsize_s *v, vfd_s* vfd)
+{
+ if (v->vsize >= vfd->offset)
+ return;
+
+ long deficit = v->vsize - vfd->offset;
+ if (deficit < v->vsize_deficit) {
+ v->vsize_deficit = deficit;
+ _Set_required(v);
+ _Set_file(v);
+ }
+}
+
+void vsize_read(vsize_s *v, void *vfd, const char *path, const int bytes)
+{
+ vfd_s *vfd_ = vfd;
+ vfd_->offset += bytes;
+ vsize_adjust(v, vfd_);
+ v->updates++;
+}
+
+void vsize_seek(vsize_s *v, void *vfd, const long new_offset)
+{
+ //vfd_s *vfd_ = vfd;
+
+ // The file's offset can be greater than the file's current size, in which
+ // case the next write to the file will extend the file. This is referred
+ // to as creating a hole in a file and is allowed. However, this behaviour
+ // does not suit the estimation of the file size before we want to run the
+ // test.
+
+ // TODO: Implement file hole support!
+ //v->updates++;
+}
+
+void vsize_write(vsize_s *v, void *vfd, const char *path, const int bytes)
+{
+ vfd_s *vfd_ = vfd;
+ vfd_->offset += bytes;
+
+ if (v->vsize < vfd_->offset)
+ v->vsize = vfd_->offset;
+
+ v->updates++;
+}
+
+void vsize_mkdir(vsize_s *v, const char *path)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ _Set_dir(v);
+ v->updates++;
+ }
+}
+
+void vsize_rmdir(vsize_s *v, const char *path)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ _Set_required(v);
+ _Set_dir(v);
+ v->updates++;
+ }
+}
+
+void vsize_unlink(vsize_s *v, const char *path)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ _Set_required(v);
+ if (!v->is_dir) {
+ _Set_file(v);
+ _Set_unsure(v);
+ }
+ v->updates++;
+ }
+}
diff --git a/ioreplay/src/generate/vsize.h b/ioreplay/src/generate/vsize.h
new file mode 100644
index 0000000..bb1008e
--- /dev/null
+++ b/ioreplay/src/generate/vsize.h
@@ -0,0 +1,180 @@
+// Copyright 2018 Mimecast Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VSIZE_H
+#define VSIZE_H
+
+#include "../utils/utils.h"
+#include "../datas/hmap.h"
+#include "../vfd.h"
+
+/**
+ * @brief Definition of a virtual size object
+ *
+ * The virtual size is used to determine the expected type and size of a file.
+ * This piece of information will be added to the INIT section of the the
+ * .replay file. That file then will be created during test initialisation.
+ * before running the test. It is very likely the case that the test requires
+ * a file of a certain size already to be present, so it can be read from disk.
+ */
+typedef struct vsize_s_ {
+ char *path; /**< The path to the file/directory */
+ off_t offset; /**< The current file offset */
+ unsigned long id; /**< The vsize id */
+ void *generate; /**< A pointer to the generate object */
+ long vsize; /**< The virtual size */
+ long vsize_deficit; /**< Size to use for file creating during init mode */
+ bool renamed; /**< True if file/dir has been renamed */
+ bool required; /**< True if init mode will create this file/dir */
+ bool is_dir; /**< True if this file/dir is a directory */
+ bool is_file; /**< True if this file/dir is a regular file */
+ bool unsure; /**< True if the file type is not fully clear */
+ long updates; /**< Amount of times this vsize has been updated */
+ bool inserted; /**< For debugging purposes only */
+} vsize_s;
+
+/**
+ * @brief Creates a new vsize object
+ *
+ * @param file_path The corresponding file path
+ * @param id The vsize vsize aka ID
+ * @param generate The generate object
+ * @return The new vsize object
+ */
+vsize_s* vsize_new(char *file_path, const unsigned long id, void *generate);
+
+/**
+ * @brief Destroys a vsize object
+ *
+ * @param v The vsize object
+ */
+void vsize_destroy(vsize_s *v);
+
+/**
+ * @brief Ensures that the parent directory exists
+ *
+ * This function ensures that the parent directory exists as a vsize object!
+ *
+ * @param v The vsize object
+ * @param path The given path
+ */
+void init_parent_dir(vsize_s *v, const char *path);
+
+/**
+ * @brief Adjusts the vsize
+ *
+ * Compares the virtual file size of the file in the vsize
+ * object to the the offset in the virtual file descriptor.
+ * In case the offset is higher we have a size deficit and
+ * we need to mark it. That way ioreplay can ensure that
+ * during init mode it will create a file with the correct
+ * size prior of running the test!
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual file descriptor object
+ */
+void vsize_adjust(vsize_s *v, vfd_s* vfd);
+
+/**
+ * @brief Adjust vsize on open
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual file descriptor object
+ * @param path The file open path
+ * @param flags The file open flags
+ */
+void vsize_open(vsize_s *v, void *vfd, const char *path, const int flags);
+
+/**
+ * @brief Adjust vsize on close
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual file descriptor object
+ */
+void vsize_close(vsize_s *v, void *vfd);
+
+/**
+ * @brief Adjust vsize on stat
+ *
+ * @param v The virtual size object
+ * @param path The stat path
+ */
+void vsize_stat(vsize_s *v, const char *path);
+
+/**
+ * @brief Adjust vsize on rename
+ *
+ * @param v The virtual size object
+ * @param v2 The virtual size object of path2
+ * @param path The first file path
+ * @param path2 The second file path
+ */
+void vsize_rename(vsize_s *v, vsize_s *v2,
+ const char *path, const char *path2);
+
+/**
+ * @brief Adjust vsize on read
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual vile descriptor object
+ * @param path The file path
+ * @param bytes The amount of bytes read
+ */
+void vsize_read(vsize_s *v, void *vfd, const char *path, const int bytes);
+
+/**
+ * @brief Adjust vsize on seek
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual vile descriptor object
+ * @param new_offset The new file offset after seek
+ */
+void vsize_seek(vsize_s *v, void *vfd, const long new_offset);
+
+/**
+ * @brief Adjust vsize on write
+ *
+ * @param v The virtual size object
+ * @param vfd The virtual vile descriptor object
+ * @param path The file path
+ * @param bytes The amount of bytes written
+ */
+void vsize_write(vsize_s *v, void *vfd, const char *path, const int bytes);
+
+/**
+ * @brief Adjust vsize on mkdir
+ *
+ * @param v The virtual size object
+ * @param path The directory path
+ */
+void vsize_mkdir(vsize_s *v, const char *path);
+
+/**
+ * @brief Adjust vsize on rmdir
+ *
+ * @param v The virtual size object
+ * @param path The directory path
+ */
+void vsize_rmdir(vsize_s *v, const char *path);
+
+/**
+ * @brief Adjust vsize on unlink
+ *
+ * @param v The virtual size object
+ * @param path The file path
+ */
+void vsize_unlink(vsize_s *v, const char *path);
+
+#endif // VSIZE_H
+