diff options
| -rw-r--r-- | AGENTS.md | 13 | ||||
| -rw-r--r-- | ioriot/src/defaults.h | 4 | ||||
| -rw-r--r-- | ioriot/src/generate/generate.c | 15 | ||||
| -rw-r--r-- | ioriot/src/generate/gioop.c | 16 | ||||
| -rw-r--r-- | ioriot/src/generate/gparser.c | 26 | ||||
| -rw-r--r-- | ioriot/src/generate/vsize.c | 34 | ||||
| -rw-r--r-- | ioriot/src/generate/vsize.h | 12 | ||||
| -rw-r--r-- | ioriot/src/init/init.c | 26 | ||||
| -rw-r--r-- | ioriot/src/init/itask.c | 16 | ||||
| -rw-r--r-- | ioriot/src/init/itask.h | 2 | ||||
| -rw-r--r-- | ioriot/src/init/ithread.c | 6 | ||||
| -rw-r--r-- | ioriot/src/mounts.c | 236 | ||||
| -rw-r--r-- | ioriot/src/mounts.h | 21 | ||||
| -rw-r--r-- | ioriot/src/utils/futils.c | 131 | ||||
| -rw-r--r-- | ioriot/src/utils/futils.h | 14 | ||||
| -rw-r--r-- | systemtap/src/ioriot.stp | 22 | ||||
| -rw-r--r-- | systemtap/src/javaioriot.stp | 22 | ||||
| -rw-r--r-- | systemtap/src/targetedioriot.stp | 22 |
18 files changed, 552 insertions, 86 deletions
@@ -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] } } |
