summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-19 13:20:25 +0200
committerPaul Buetow <paul@buetow.org>2026-03-19 13:20:25 +0200
commitb328b5ab1bfa8f4346cbdda30af731ed244268cb (patch)
tree4fbe7cbed3a6775c33e5833c54d6f45448cc91e5
parenta169b912b7fdaf78bbc4e68c7739b054918472bf (diff)
fix: replay symlinks within the test root
-rw-r--r--AGENTS.md13
-rw-r--r--ioriot/src/defaults.h4
-rw-r--r--ioriot/src/generate/generate.c15
-rw-r--r--ioriot/src/generate/gioop.c16
-rw-r--r--ioriot/src/generate/gparser.c26
-rw-r--r--ioriot/src/generate/vsize.c34
-rw-r--r--ioriot/src/generate/vsize.h12
-rw-r--r--ioriot/src/init/init.c26
-rw-r--r--ioriot/src/init/itask.c16
-rw-r--r--ioriot/src/init/itask.h2
-rw-r--r--ioriot/src/init/ithread.c6
-rw-r--r--ioriot/src/mounts.c236
-rw-r--r--ioriot/src/mounts.h21
-rw-r--r--ioriot/src/utils/futils.c131
-rw-r--r--ioriot/src/utils/futils.h14
-rw-r--r--systemtap/src/ioriot.stp22
-rw-r--r--systemtap/src/javaioriot.stp22
-rw-r--r--systemtap/src/targetedioriot.stp22
18 files changed, 552 insertions, 86 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 0929bdc..48be3ce 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,10 +41,15 @@ workload.
- Run from the repository root: `/home/paul/git/ioriot`.
- Use a base directory under `/home`, not `/tmp`. This project filters some
file systems during replay generation, and `/tmp` may be `tmpfs`.
+- Keep the helper base directory and test name short. The checked-in syscall
+ helpers use fixed-size `readlink()` buffers, so overly long absolute paths
+ can truncate the captured symlink target and turn a valid replay check into a
+ false failure.
- The helper below is intentionally dirfd-heavy so it catches path-resolution
bugs in `*at` syscalls as well as normal file I/O.
-- `readdir` is intentionally excluded. Generation handles it, but replay does
- not currently have a `READDIR` implementation.
+- The main helper exercises the native x86_64 syscall surface. Use the compat32
+ helper below when you need the legacy 32-bit ABI names such as `readdir`,
+ `llseek`, `statfs64`, and the `*chown16` family.
### 1. Rebuild, test, and install
@@ -95,6 +100,10 @@ sudo /opt/ioriot/bin/ioriot -c "$capture_file" -x "$helper_pid"
```
Wait until `staprun` reports that `targetedioriot.ko` has been inserted.
+Then give the module a short settling delay before releasing the helper, for
+example `sleep 2`. On this host, releasing the workload immediately after the
+inserted message can miss the first burst of syscalls and produce a nearly
+empty `.capture`.
### 5. Release the helper and stop capture after it exits
diff --git a/ioriot/src/defaults.h b/ioriot/src/defaults.h
index 404f83a..395f295 100644
--- a/ioriot/src/defaults.h
+++ b/ioriot/src/defaults.h
@@ -18,9 +18,9 @@
#include "utils/utils.h"
/** Version of the supported .capture format */
-#define CAPTURE_VERSION 3
+#define CAPTURE_VERSION 4
/** Version of the supported .replay format */
-#define REPLAY_VERSION 2
+#define REPLAY_VERSION 3
/** Meta header key for the supported .replay format version */
#define REPLAY_VERSION_KEY "replay_version"
/** Max amount of tokens per line in the .capture file */
diff --git a/ioriot/src/generate/generate.c b/ioriot/src/generate/generate.c
index 65e07cc..cbf0f2d 100644
--- a/ioriot/src/generate/generate.c
+++ b/ioriot/src/generate/generate.c
@@ -212,8 +212,8 @@ void _write_ranges_cb(long start, void *data, void *data2)
long end = (long) data;
long bytes = end-start;
if (bytes > 0) {
- fprintf(g->replay_fd, "%d|%d|%ld|%ld|%s|\n",
- v->is_dir, v->is_file, start, bytes, v->path);
+ fprintf(g->replay_fd, "%d|%d|%d|%ld|%ld|%s|\n",
+ v->is_dir, v->is_file, v->is_link, start, bytes, v->path);
}
}
@@ -223,11 +223,16 @@ void generate_write_init_cb(void *data)
generate_s *g = v->generate;
if (v->required && strlen(v->path) > 0) {
- if (v->read_ranges) {
+ if (v->is_link && v->link_target) {
+ fprintf(g->replay_fd, "%d|%d|%d|%ld|%ld|%s|%s|\n",
+ v->is_dir, v->is_file, v->is_link,
+ 0L, 0L, v->path, v->link_target);
+ } else if (v->read_ranges) {
btree_run_cb2(v->read_ranges, _write_ranges_cb, data);
} else if (v->bytes >= 0) {
- fprintf(g->replay_fd, "%d|%d|%ld|%ld|%s|\n",
- v->is_dir, v->is_file, 0L, v->bytes, v->path);
+ fprintf(g->replay_fd, "%d|%d|%d|%ld|%ld|%s|\n",
+ v->is_dir, v->is_file, v->is_link,
+ 0L, v->bytes, v->path);
}
}
}
diff --git a/ioriot/src/generate/gioop.c b/ioriot/src/generate/gioop.c
index 887496b..13d849a 100644
--- a/ioriot/src/generate/gioop.c
+++ b/ioriot/src/generate/gioop.c
@@ -491,8 +491,12 @@ status_e gioop_readlink(gwriter_s *w, gtask_s *t, generate_s *g)
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);
+ if (t->status >= 0) {
+ if (t->path2 != NULL)
+ vsize_symlink(t->vsize, t->path, t->path2);
+ else
+ vsize_stat(t->vsize, t->path);
+ }
return SUCCESS;
}
@@ -506,8 +510,12 @@ status_e gioop_readlinkat(gwriter_s *w, gtask_s *t, generate_s *g)
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);
+ if (t->status >= 0) {
+ if (t->path2 != NULL)
+ vsize_symlink(t->vsize, t->path, t->path2);
+ else
+ vsize_stat(t->vsize, t->path);
+ }
return SUCCESS;
}
diff --git a/ioriot/src/generate/gparser.c b/ioriot/src/generate/gparser.c
index b9e91de..6626bef 100644
--- a/ioriot/src/generate/gparser.c
+++ b/ioriot/src/generate/gparser.c
@@ -74,6 +74,7 @@ void gparser_extract(gparser_s *p, gtask_s *t)
{
status_e ret = SUCCESS;
generate_s *g = p->generate;
+ char *original_path = NULL;
char *saveptr;
char* tok = strtok2_r(t->line, ";:,", &saveptr);
@@ -108,6 +109,7 @@ void gparser_extract(gparser_s *p, gtask_s *t)
// run multiple tests simoultaneously.
if (t->path) {
+ original_path = Clone(t->path);
if (!mounts_transform_path(g->mps, g->name,
t->path, &t->path_r)) {
Cleanup(ERROR);
@@ -117,12 +119,23 @@ void gparser_extract(gparser_s *p, gtask_s *t)
}
if (t->path2) {
- if (!mounts_transform_path(g->mps, g->name,
- t->path2, &t->path2_r)) {
- Cleanup(ERROR);
+ if (Eq(t->op, "readlink") || Eq(t->op, "readlinkat")) {
+ if (original_path && t->path &&
+ mounts_transform_symlink_target(g->mps, g->name,
+ original_path, t->path,
+ t->path2, &t->path2_r)) {
+ t->path2 = t->path2_r;
+ } else {
+ t->path2 = NULL;
+ }
+ } else {
+ if (!mounts_transform_path(g->mps, g->name,
+ t->path2, &t->path2_r)) {
+ Cleanup(ERROR);
+ }
+ if (t->path2_r)
+ t->path2 = t->path2_r;
}
- if (t->path2_r)
- t->path2 = t->path2_r;
}
}
@@ -134,6 +147,9 @@ cleanup:
#ifdef LOG_FILTERED
t->filtered_where = __FILE__;
#endif
+
+ if (original_path)
+ free(original_path);
}
status_e gparser_extract_tok(gparser_s *p, gtask_s *t, char *tok)
diff --git a/ioriot/src/generate/vsize.c b/ioriot/src/generate/vsize.c
index b38f1bc..c306a4a 100644
--- a/ioriot/src/generate/vsize.c
+++ b/ioriot/src/generate/vsize.c
@@ -17,8 +17,19 @@
#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_file(v) \
+ v->is_file = true; \
+ v->is_link = false; \
+ v->unsure = v->is_dir = false
+#define _Set_dir(v) \
+ v->is_dir = true; \
+ v->is_link = false; \
+ v->unsure = v->is_file = false
+#define _Set_link(v) \
+ v->is_link = true; \
+ v->is_dir = false; \
+ v->is_file = false; \
+ v->unsure = false
#define _Set_unsure(v) v->unsure = true
#define _Set_inserted(v) v->inserted = true
#define _Set_renamed(v) v->renamed = true
@@ -34,6 +45,8 @@ vsize_s* vsize_new(char *file_path, const unsigned long id,
v->inserted = false;
v->is_dir = false;
v->is_file = false;
+ v->is_link = false;
+ v->link_target = NULL;
v->path = Clone(file_path);
v->renamed = false;
v->required = false;
@@ -56,6 +69,8 @@ void vsize_destroy(vsize_s *v)
if (v->write_ranges)
btree_destroy(v->write_ranges);
+ if (v->link_target)
+ free(v->link_target);
free(v->path);
free(v);
}
@@ -147,6 +162,21 @@ void vsize_stat(vsize_s *v, const char *path)
}
}
+void vsize_symlink(vsize_s *v, const char *path, const char *target)
+{
+ if (v->updates == 0) {
+ init_parent_dir(v, path);
+ }
+
+ if (v->link_target)
+ free(v->link_target);
+
+ v->link_target = Clone(target);
+ _Set_required(v);
+ _Set_link(v);
+ v->updates++;
+}
+
void vsize_rename(vsize_s *v, vsize_s *v2,
const char *path, const char *path2)
{
diff --git a/ioriot/src/generate/vsize.h b/ioriot/src/generate/vsize.h
index ffda9e5..a60fc52 100644
--- a/ioriot/src/generate/vsize.h
+++ b/ioriot/src/generate/vsize.h
@@ -30,6 +30,7 @@
*/
typedef struct vsize_s_ {
char *path; /**< The path to the file/directory */
+ char *link_target; /**< Relative replay-local symlink target */
long bytes; /**< The virtual size */
btree_s *read_ranges; /**< Used to store used data ranges in a file with holes */
btree_s *write_ranges; /**< Used to store used data ranges in a file with holes */
@@ -39,6 +40,7 @@ typedef struct vsize_s_ {
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 is_link; /**< True if this path is a symbolic link */
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 */
@@ -112,6 +114,15 @@ void vsize_close(vsize_s *v, void *vfd);
void vsize_stat(vsize_s *v, const char *path);
/**
+ * @brief Adjust vsize on readlink
+ *
+ * @param v The virtual size object
+ * @param path The symlink path
+ * @param target The safe replay-local relative target
+ */
+void vsize_symlink(vsize_s *v, const char *path, const char *target);
+
+/**
* @brief Adjust vsize on rename
*
* @param v The virtual size object
@@ -176,4 +187,3 @@ void vsize_rmdir(vsize_s *v, const char *path);
void vsize_unlink(vsize_s *v, const char *path);
#endif // VSIZE_H
-
diff --git a/ioriot/src/init/init.c b/ioriot/src/init/init.c
index 659d59a..afca5ee 100644
--- a/ioriot/src/init/init.c
+++ b/ioriot/src/init/init.c
@@ -116,10 +116,11 @@ status_e init_run(options_s *opts)
// Seek to the INIT section
fseeko(i->replay_fd, init_offset, SEEK_SET);
- bool is_file = false, is_dir = false;
+ bool is_file = false, is_dir = false, is_link = false;
long offset = 0;
long bytes = 0;
char *path;
+ char *target = NULL;
// Stats
long dirs_created = 0;
@@ -137,6 +138,14 @@ status_e init_run(options_s *opts)
while ((read = getline(&line, &len, i->replay_fd)) != -1) {
//Debug(line);
+ is_dir = false;
+ is_file = false;
+ is_link = false;
+ offset = 0;
+ bytes = 0;
+ path = NULL;
+ target = NULL;
+
char *tok = strtok_r(line, "|", &saveptr);
for (int ntok = 0; tok; ntok++) {
@@ -148,20 +157,26 @@ status_e init_run(options_s *opts)
is_file = atoi(tok) == 1;
break;
case 2:
+ is_link = atoi(tok) == 1;
+ break;
+ case 3:
offset = atol(tok);
if (offset < 0) {
Error("Offset overflow: '%ld'", offset);
}
break;
- case 3:
+ case 4:
bytes = atol(tok);
if (bytes < 0) {
Error("Size overflow: '%ld'", bytes);
}
break;
- case 4:
+ case 5:
path = tok;
break;
+ case 6:
+ target = tok;
+ break;
default:
break;
}
@@ -183,12 +198,17 @@ status_e init_run(options_s *opts)
if (is_dir) {
task->is_dir = true;
+ } else if (is_link) {
+ task->is_link = true;
+
} else if (is_file) {
task->is_file = true;
task->bytes = bytes;
task->offset = offset;
}
task->path = Clone(path);
+ if (task->is_link && target)
+ task->target = Clone(target);
// We run one init thread per mount point
int mnr = mounts_get_mountnumber(i->mounts, path);
diff --git a/ioriot/src/init/itask.c b/ioriot/src/init/itask.c
index de7c551..84425b8 100644
--- a/ioriot/src/init/itask.c
+++ b/ioriot/src/init/itask.c
@@ -19,6 +19,7 @@ itask_s* itask_new()
itask_s *task = Malloc(itask_s);
task->path = NULL;
+ task->target = NULL;
itask_reset_stats(task);
return task;
@@ -28,13 +29,15 @@ void itask_destroy(itask_s *task)
{
if (task->path)
free(task->path);
+ if (task->target)
+ free(task->target);
free(task);
}
void itask_reset_stats(itask_s *task)
{
- task->is_dir = task->is_file = false;
+ task->is_dir = task->is_file = task->is_link = false;
task->sizes_created = task->offset = task->bytes = 0;
task->dirs_created = task->files_created = 0;
@@ -42,6 +45,10 @@ void itask_reset_stats(itask_s *task)
free(task->path);
task->path = NULL;
}
+ if (task->target) {
+ free(task->target);
+ task->target = NULL;
+ }
}
void itask_extract_stats(itask_s *task, long* dirs_created, long *files_created,
@@ -60,7 +67,8 @@ void itask_extract_stats(itask_s *task, long* dirs_created, long *files_created,
void itask_print(itask_s *task)
{
- Put("itask(%p): is_dir:%d is_file:%d offset:%ld bytes:%ld path:%s",
- (void*)task, task->is_dir, task->is_file,
- task->offset, task->bytes, task->path);
+ Put("itask(%p): is_dir:%d is_file:%d is_link:%d offset:%ld bytes:%ld "
+ "path:%s target:%s",
+ (void*)task, task->is_dir, task->is_file, task->is_link,
+ task->offset, task->bytes, task->path, task->target);
}
diff --git a/ioriot/src/init/itask.h b/ioriot/src/init/itask.h
index 21afba4..f6c567b 100644
--- a/ioriot/src/init/itask.h
+++ b/ioriot/src/init/itask.h
@@ -23,9 +23,11 @@
typedef struct itask_s_ {
bool is_dir;
bool is_file;
+ bool is_link;
long offset;
long bytes;
char *path;
+ char *target;
long dirs_created;
long files_created;
long sizes_created;
diff --git a/ioriot/src/init/ithread.c b/ioriot/src/init/ithread.c
index 953fe14..6f91b64 100644
--- a/ioriot/src/init/ithread.c
+++ b/ioriot/src/init/ithread.c
@@ -85,6 +85,12 @@ void ithread_run_task(ithread_s *t, itask_s *task)
if (task->is_dir) {
task->dirs_created += ensure_dir_exists(task->path);
+ } else if (task->is_link) {
+ if (!ensure_relative_symlink_exists(task->path, task->target,
+ &task->dirs_created)) {
+ task->files_created++;
+ }
+
} else if (task->is_file) {
if (!ensure_file_exists(task->path, &task->dirs_created)) {
task->files_created++;
diff --git a/ioriot/src/mounts.c b/ioriot/src/mounts.c
index d754557..53b8abe 100644
--- a/ioriot/src/mounts.c
+++ b/ioriot/src/mounts.c
@@ -19,6 +19,139 @@
#define _PATH_INSERT "/.ioriot/"
#define _PATH_INSERT_LEN 11 // strlen of _PATH_INSERT
+static char*
+_mounts_normalize_path(const char *path)
+{
+ char *tmp = NULL;
+
+ if (path == NULL)
+ return NULL;
+
+ if (!strstr(path, "..") && !strstr(path, "//"))
+ return Clone(path);
+
+ tmp = Calloc(strlen(path) + 1, char);
+
+ stack_s *s = stack_new();
+ char *clone = Clone(path);
+ char *saveptr = NULL;
+ char *tok = strtok_r(clone, "/", &saveptr);
+
+ while (tok) {
+ if (strcmp(tok, "..") == 0) {
+ stack_pop(s);
+ } else if (strcmp(tok, ".") != 0 && strlen(tok) > 0) {
+ stack_push(s, tok);
+ }
+ tok = strtok_r(NULL, "/", &saveptr);
+ }
+
+ if (stack_is_empty(s)) {
+ strcpy(tmp, ".");
+ } else {
+ s = stack_new_reverse_from(s);
+ strcpy(tmp, "/");
+ strcat(tmp, (char*)stack_pop(s));
+
+ while (!stack_is_empty(s)) {
+ strcat(tmp, "/");
+ strcat(tmp, (char*)stack_pop(s));
+ }
+ }
+
+ stack_destroy(s);
+ free(clone);
+
+ return tmp;
+}
+
+static char*
+_mounts_replay_root_from_transformed(const char *path, const char *name)
+{
+ char *marker = NULL;
+ char *root = NULL;
+ char *pos = NULL;
+
+ if (asprintf(&marker, "%s%s", _PATH_INSERT, name) == -1) {
+ Error("Could not allocate replay root marker");
+ }
+
+ pos = strstr(path, marker);
+ if (pos == NULL) {
+ free(marker);
+ return NULL;
+ }
+
+ int root_len = (pos - path) + strlen(marker);
+ root = Calloc(root_len + 1, char);
+ strncpy(root, path, root_len);
+ root[root_len] = '\0';
+
+ free(marker);
+ return root;
+}
+
+static char*
+_mounts_relative_path(const char *from_dir, const char *to_path)
+{
+ char *from = Clone(from_dir);
+ char *to = Clone(to_path);
+ char *from_parts[1024];
+ char *to_parts[1024];
+ int from_count = 0;
+ int to_count = 0;
+ int common = 0;
+ char *saveptr = NULL;
+ char *tok = NULL;
+ char *result = NULL;
+ size_t size = 2;
+
+ tok = strtok_r(from, "/", &saveptr);
+ while (tok && from_count < 1024) {
+ from_parts[from_count++] = tok;
+ tok = strtok_r(NULL, "/", &saveptr);
+ }
+
+ saveptr = NULL;
+ tok = strtok_r(to, "/", &saveptr);
+ while (tok && to_count < 1024) {
+ to_parts[to_count++] = tok;
+ tok = strtok_r(NULL, "/", &saveptr);
+ }
+
+ while (common < from_count && common < to_count &&
+ Eq(from_parts[common], to_parts[common])) {
+ common++;
+ }
+
+ for (int i = common; i < from_count; ++i)
+ size += 3;
+ for (int i = common; i < to_count; ++i)
+ size += strlen(to_parts[i]) + 1;
+
+ result = Calloc(size, char);
+
+ if (common == from_count && common == to_count) {
+ strcpy(result, ".");
+ goto cleanup;
+ }
+
+ for (int i = common; i < from_count; ++i)
+ strcat(result, "../");
+
+ for (int i = common; i < to_count; ++i) {
+ if (strlen(result) > 0 && result[strlen(result) - 1] != '/')
+ strcat(result, "/");
+ strcat(result, to_parts[i]);
+ }
+
+cleanup:
+ free(from);
+ free(to);
+
+ return result;
+}
+
void mounts_read(mounts_s *m)
{
char *mounts = "/proc/mounts";
@@ -288,49 +421,8 @@ bool mounts_transform_path(mounts_s *m, const char *name,
// transform '/foo/bar/../' into '/foo/'.
// Also remove double '/' from paths.
- if (strstr(path, "..") || strstr(path, "//")) {
- // tmp will be freed under label 'cleanup' at end of function.
- tmp = Calloc(strlen(path)+1, char);
-
- // stack to put the tokens on
- stack_s *s = stack_new();
-
- // we need a copy of the path, so we can tokenize it into the stack
- char* clone = Clone(path);
-
- char *saveptr = NULL;
- char *tok = strtok_r(clone, "/", &saveptr);
-
- // Add each part of the path to the stack.
- while (tok) {
- if (strcmp(tok, "..") == 0) {
- stack_pop(s);
- } else {
- stack_push(s, tok);
- }
- tok = strtok_r(NULL, "/", &saveptr);
- }
-
- if (stack_is_empty(s)) {
- strcpy(tmp, ".");
-
- } else {
- s = stack_new_reverse_from(s);
- strcpy(tmp, "/");
- strcat(tmp, (char*)stack_pop(s));
-
- while(!stack_is_empty(s)) {
- strcat(tmp, "/");
- strcat(tmp, (char*)stack_pop(s));
- }
- }
-
- stack_destroy(s);
- free(clone);
-
- // This is the path without '..' and '//' (and '///' ... etc')
- path = tmp;
- }
+ tmp = _mounts_normalize_path(path);
+ path = tmp;
// Now heck whether the path is on a supported file system. If not, ignore!
if (mounts_ignore_path(m, path)) {
@@ -386,6 +478,64 @@ cleanup:
return line_ok;
}
+bool mounts_transform_symlink_target(mounts_s *m, const char *name,
+ const char *link_path,
+ const char *link_path_r,
+ const char *target,
+ char **target_r)
+{
+ bool ok = false;
+ char *target_abs = NULL;
+ char *target_path_r = NULL;
+ char *link_root = NULL;
+ char *target_root = NULL;
+ char *link_dir = NULL;
+
+ if (link_path == NULL || link_path_r == NULL || target == NULL)
+ return false;
+
+ if (target[0] == '/') {
+ target_abs = _mounts_normalize_path(target);
+ } else {
+ char *dir = dirname_r(Clone(link_path));
+ char *joined = NULL;
+ if (asprintf(&joined, "%s/%s", dir, target) == -1) {
+ Error("Could not allocate symlink target path");
+ }
+ target_abs = _mounts_normalize_path(joined);
+ free(joined);
+ free(dir);
+ }
+
+ if (!mounts_transform_path(m, name, target_abs, &target_path_r))
+ goto cleanup;
+
+ link_root = _mounts_replay_root_from_transformed(link_path_r, name);
+ target_root = _mounts_replay_root_from_transformed(target_path_r, name);
+ if (link_root == NULL || target_root == NULL || !Eq(link_root, target_root))
+ goto cleanup;
+
+ link_dir = dirname_r(Clone(link_path_r));
+ *target_r = _mounts_relative_path(link_dir, target_path_r);
+ ok = (*target_r != NULL);
+
+cleanup:
+ if (!ok && target_r)
+ *target_r = NULL;
+ if (target_abs)
+ free(target_abs);
+ if (target_path_r)
+ free(target_path_r);
+ if (link_root)
+ free(link_root);
+ if (target_root)
+ free(target_root);
+ if (link_dir)
+ free(link_dir);
+
+ return ok;
+}
+
int mounts_get_mountnumber(mounts_s *m, const char *path)
{
for (int i = m->count-1; i >= 0; --i) {
diff --git a/ioriot/src/mounts.h b/ioriot/src/mounts.h
index 1c4a3d4..726e4b1 100644
--- a/ioriot/src/mounts.h
+++ b/ioriot/src/mounts.h
@@ -138,6 +138,27 @@ bool mounts_ignore_path(mounts_s *m, const char *path);
bool mounts_transform_path(mounts_s *m, const char *name,
char *path, char **path_r);
+/**
+ * @brief Rewrites a symlink target into a replay-local relative target
+ *
+ * The returned target is always relative to the replay symlink location and
+ * is only produced when both link and target stay inside the same replay
+ * root.
+ *
+ * @param m The responsible mountpoint object
+ * @param name The name of the test
+ * @param link_path The original absolute symlink path from capture
+ * @param link_path_r The transformed replay symlink path
+ * @param target The raw readlink target captured from the host
+ * @param target_r The safe replay-local relative target
+ * @return False if the target can not be rewritten safely
+ */
+bool mounts_transform_symlink_target(mounts_s *m, const char *name,
+ const char *link_path,
+ const char *link_path_r,
+ const char *target,
+ char **target_r);
+
/**
* @brief Get's the mount point number of a path
diff --git a/ioriot/src/utils/futils.c b/ioriot/src/utils/futils.c
index 5cbe4e1..8fd38a7 100644
--- a/ioriot/src/utils/futils.c
+++ b/ioriot/src/utils/futils.c
@@ -21,6 +21,87 @@
#include "../macros.h"
+static char*
+_normalize_path(const char *path)
+{
+ if (path == NULL)
+ return NULL;
+
+ char *copy = Clone(path);
+ char *parts[1024];
+ int count = 0;
+ bool absolute = (path[0] == '/');
+ char *saveptr = NULL;
+ char *tok = strtok_r(copy, "/", &saveptr);
+
+ while (tok && count < 1024) {
+ if (strcmp(tok, "..") == 0) {
+ if (count > 0)
+ count--;
+ } else if (strcmp(tok, ".") != 0 && strlen(tok) > 0) {
+ parts[count++] = tok;
+ }
+ tok = strtok_r(NULL, "/", &saveptr);
+ }
+
+ size_t len = absolute ? 2 : 1;
+ for (int i = 0; i < count; ++i)
+ len += strlen(parts[i]) + 1;
+
+ char *normalized = Calloc(len, char);
+
+ if (absolute)
+ strcpy(normalized, "/");
+
+ for (int i = 0; i < count; ++i) {
+ if (strlen(normalized) > 0 &&
+ normalized[strlen(normalized) - 1] != '/') {
+ strcat(normalized, "/");
+ }
+ strcat(normalized, parts[i]);
+ }
+
+ if (strlen(normalized) == 0)
+ strcpy(normalized, absolute ? "/" : ".");
+
+ free(copy);
+ return normalized;
+}
+
+static char*
+_replay_root_from_path(const char *path)
+{
+ const char *marker = "/.ioriot/";
+ char *pos = strstr(path, marker);
+ char *next = NULL;
+ char *root = NULL;
+
+ if (pos == NULL)
+ return NULL;
+
+ next = strchr(pos + strlen(marker), '/');
+ if (next == NULL)
+ return NULL;
+
+ int root_len = next - path;
+ root = Calloc(root_len + 1, char);
+ strncpy(root, path, root_len);
+ root[root_len] = '\0';
+
+ return root;
+}
+
+static bool
+_path_within_dir(const char *path, const char *dir)
+{
+ int len = strlen(dir);
+
+ if (strncmp(path, dir, len) != 0)
+ return false;
+
+ return (path[len] == '\0' || path[len] == '/');
+}
+
void _write_random_to_stream(FILE *fp, unsigned long bytes)
{
char *buf = NULL;
@@ -152,6 +233,56 @@ int ensure_file_exists(char *path, long *num_dirs_created)
return ERROR;
}
+int ensure_relative_symlink_exists(char *path, const char *target,
+ long *num_dirs_created)
+{
+ if (target == NULL || target[0] == '\0' || target[0] == '/')
+ return ERROR;
+
+ char *dirname = dirname_r(Clone(path));
+ *num_dirs_created += ensure_dir_exists(dirname);
+
+ char *joined = NULL;
+ if (asprintf(&joined, "%s/%s", dirname, target) == -1) {
+ free(dirname);
+ Error("Could not allocate symlink target path");
+ }
+
+ char *resolved = _normalize_path(joined);
+ char *root = _replay_root_from_path(path);
+ free(joined);
+
+ if (root == NULL || !_path_within_dir(resolved, root)) {
+ free(dirname);
+ free(resolved);
+ if (root)
+ free(root);
+ return ERROR;
+ }
+
+ struct stat path_stat;
+ if (lstat(path, &path_stat) == 0) {
+ if (S_ISDIR(path_stat.st_mode)) {
+ free(dirname);
+ free(resolved);
+ free(root);
+ return ERROR;
+ }
+ unlink(path);
+ }
+
+ int ret = symlink(target, path);
+
+ free(dirname);
+ free(resolved);
+ free(root);
+
+ if (ret == -1)
+ return ERROR;
+
+ return SUCCESS;
+}
+
char* dirname_r(char *path)
{
int len = strlen(path);
diff --git a/ioriot/src/utils/futils.h b/ioriot/src/utils/futils.h
index e8cff97..96b1c9e 100644
--- a/ioriot/src/utils/futils.h
+++ b/ioriot/src/utils/futils.h
@@ -35,6 +35,20 @@ char* dirname_r(char *path);
int ensure_file_exists(char *path, long *num_dirs_created);
/**
+ * @brief Ensures that a replay-local relative symlink exists
+ *
+ * The target must be relative and must resolve inside the same replay root as
+ * the symlink itself.
+ *
+ * @param path The symlink path
+ * @param target The relative symlink target
+ * @param num_dirs_created Holds a count of how many sub dirs have been created
+ * @return -1 on error, 0 on success.
+ */
+int ensure_relative_symlink_exists(char *path, const char *target,
+ long *num_dirs_created);
+
+/**
* @brief Checks whether path exists
*
* @param path The path
diff --git a/systemtap/src/ioriot.stp b/systemtap/src/ioriot.stp
index 9103a25..d4ad63e 100644
--- a/systemtap/src/ioriot.stp
+++ b/systemtap/src/ioriot.stp
@@ -99,7 +99,7 @@ probe timer.s(3600) {
}
probe begin {
- printf("#|capture_version=%d|\n", 3);
+ printf("#|capture_version=%d|\n", 4);
}
# --- open ---
@@ -470,48 +470,60 @@ probe syscall.readdir.return {
}
# --- readlink ---
-# Tapset entry vars: path_unquoted
+# Tapset entry vars: path_unquoted, buf_uaddr
probe syscall.readlink {
if (execname() != "stapio") {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlink.return {
if(execname() != "stapio") {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path(ENTRY_PATH[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}
# --- readlinkat ---
-# Tapset entry vars: dfd, path_unquoted
+# Tapset entry vars: dfd, path_unquoted, buf_uaddr
probe syscall.readlinkat {
if (execname() != "stapio") {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_FD[tid(),name] = dfd
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlinkat.return {
if(execname() != "stapio") {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path_at(ENTRY_PATH[tid(),name], ENTRY_FD[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_FD[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}
diff --git a/systemtap/src/javaioriot.stp b/systemtap/src/javaioriot.stp
index 0c59bf2..9ddb045 100644
--- a/systemtap/src/javaioriot.stp
+++ b/systemtap/src/javaioriot.stp
@@ -99,7 +99,7 @@ probe timer.s(3600) {
}
probe begin {
- printf("#|capture_version=%d|\n", 3);
+ printf("#|capture_version=%d|\n", 4);
}
# --- open ---
@@ -470,48 +470,60 @@ probe syscall.readdir.return {
}
# --- readlink ---
-# Tapset entry vars: path_unquoted
+# Tapset entry vars: path_unquoted, buf_uaddr
probe syscall.readlink {
if (execname() == "java") {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlink.return {
if(execname() == "java") {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path(ENTRY_PATH[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}
# --- readlinkat ---
-# Tapset entry vars: dfd, path_unquoted
+# Tapset entry vars: dfd, path_unquoted, buf_uaddr
probe syscall.readlinkat {
if (execname() == "java") {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_FD[tid(),name] = dfd
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlinkat.return {
if(execname() == "java") {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path_at(ENTRY_PATH[tid(),name], ENTRY_FD[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_FD[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}
diff --git a/systemtap/src/targetedioriot.stp b/systemtap/src/targetedioriot.stp
index 7253b65..51f68c0 100644
--- a/systemtap/src/targetedioriot.stp
+++ b/systemtap/src/targetedioriot.stp
@@ -99,7 +99,7 @@ probe timer.s(3600) {
}
probe begin {
- printf("#|capture_version=%d|\n", 3);
+ printf("#|capture_version=%d|\n", 4);
}
# --- open ---
@@ -470,48 +470,60 @@ probe syscall.readdir.return {
}
# --- readlink ---
-# Tapset entry vars: path_unquoted
+# Tapset entry vars: path_unquoted, buf_uaddr
probe syscall.readlink {
if (pid() == target()) {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlink.return {
if(pid() == target()) {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path(ENTRY_PATH[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}
# --- readlinkat ---
-# Tapset entry vars: dfd, path_unquoted
+# Tapset entry vars: dfd, path_unquoted, buf_uaddr
probe syscall.readlinkat {
if (pid() == target()) {
PROBE_ENTRY_TIMES[tid(),name] = gettimeofday_ns()
ENTRY_FD[tid(),name] = dfd
ENTRY_PATH[tid(),name] = path_unquoted
+ ENTRY_ADDR[tid(),name] = buf_uaddr
}
}
probe syscall.readlinkat.return {
if(pid() == target()) {
ns = gettimeofday_ns()
- printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,s=%d;:,\n",
+ target = ""
+ if (retval > 0)
+ target = user_string_n(ENTRY_ADDR[tid(),name], retval)
+ printf("t=%ld;:,D=%ld;:,i=%d:%d;:,o=%s;:,p=%s;:,P=%s;:,s=%d;:,\n",
ns, ns-PROBE_ENTRY_TIMES[tid(),name],
pid(), tid(), name,
absolute_path_at(ENTRY_PATH[tid(),name], ENTRY_FD[tid(),name]),
+ target,
retval);
delete PROBE_ENTRY_TIMES[tid(),name]
delete ENTRY_FD[tid(),name]
delete ENTRY_PATH[tid(),name]
+ delete ENTRY_ADDR[tid(),name]
}
}