diff options
| author | Paul Bütow <pbuetow@mimecast.com> | 2018-03-01 11:21:26 +0000 |
|---|---|---|
| committer | Paul Bütow <pbuetow@mimecast.com> | 2018-03-01 11:21:26 +0000 |
| commit | 56f8cdff9aaa9bf00c5dc9441a7569374f2cbafb (patch) | |
| tree | b5b440b504b9879e241733fa38d19089fb3377b2 /ioreplay/src/mounts.c | |
initial commit0.1
Diffstat (limited to 'ioreplay/src/mounts.c')
| -rw-r--r-- | ioreplay/src/mounts.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/ioreplay/src/mounts.c b/ioreplay/src/mounts.c new file mode 100644 index 0000000..ac6f1d4 --- /dev/null +++ b/ioreplay/src/mounts.c @@ -0,0 +1,400 @@ +// 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 "mounts.h" + +#include "utils/futils.h" + +#define _PATH_INSERT "/.ioreplay/" +#define _PATH_INSERT_LEN 11 // strlen of _PATH_INSERT + +void mounts_read(mounts_s *m) +{ + char *mounts = "/proc/mounts"; + size_t len = 0; + char *line = NULL; + char *saveptr = NULL; + + Put("Reading '%s'", mounts); + + FILE *fp = Fopen(mounts, "r"); + Out("Adding supported file systems to replay paths:"); + + while (getline(&line, &len, fp) != -1) { + bool ignore = true; + + char *dev = strtok_r(line, " ", &saveptr); + if (dev == NULL) { + Error("Could not parse device from %s", mounts); + } + + char *mp = strtok_r(NULL, " ", &saveptr); + if (mp == NULL) { + Error("Could not parse mountpoint from %s", mounts); + } + + char *fs = strtok_r(NULL, " ", &saveptr); + if (fs == NULL) { + Error("Could not parse file system from %s", mounts); + } +#ifdef MP_DEBUG + Debug("fs:%s", fs); +#endif + // TODO: Make file system types configurable + if (Eq(fs, "ext2")) { + ignore = false; + } else if (Eq(fs, "ext5")) { + ignore = false; + } else if (Eq(fs, "ext4")) { + ignore = false; + } else if (Eq(fs, "xfs")) { + ignore = false; + } else if (Eq(fs, "zfs")) { + ignore = false; + } else if (Eq(fs, "btrfs")) { + ignore = false; + } + + if (ignore) { + if (strcmp(mp, "/") != 0) { + m->ignore_mps[m->ignore_count] = Clone(mp); + m->ignore_count++; + } + + } else if (m->count >= MAX_MOUNTPOINTS) { + Error("Exceeded max mount points: %d\n", m->count); + + } else { + Out(" %s (%s)", mp, fs); + m->mps[m->count] = Clone(mp); + m->lengths[m->count] = strlen(mp); + m->count++; + } + } + + fclose(fp); + Out("\n"); +} + +mounts_s *mounts_new(options_s *opts) +{ + mounts_s *m = Malloc(mounts_s); + + m->opts = opts; + m->count = 0; + m->ignore_count = 0; + mounts_read(m); + + return m; +} + +void mounts_destroy(mounts_s *m) +{ + if (!m) + return; + for (int i = 0; i < m->count; i++) + free(m->mps[i]); + free(m); +} + +void mounts_trash(mounts_s *m) +{ + options_s *opts = m->opts; + drop_root(opts->user); + Put("Moving all old files to trash (of previous tests)..."); + + struct timeval tv; + gettimeofday(&tv, NULL); + + char *wd_path = NULL; + asprintf(&wd_path, "%s/%s", opts->wd_base, opts->name); + + char *trash_path = NULL; + asprintf(&trash_path, "%s/.trash/%ld", opts->wd_base, tv.tv_sec); + + if (is_dir(wd_path)) { + ensure_dir_exists(trash_path); + chown_path(opts->user, trash_path); + if (rename(wd_path, trash_path)) { + Errno("Could not move '%s' to '%s'", wd_path, trash_path); + } + } + free(wd_path); + free(trash_path); + + for (int i = 0; i < m->count; i++) { + char *mp = m->mps[i]; + char *path = NULL; + asprintf(&path, "%s/%s/%s", mp, _PATH_INSERT, opts->name); + asprintf(&trash_path, "%s/%s/.trash/%ld", + mp, _PATH_INSERT, tv.tv_sec); + + if (is_dir(path)) { + ensure_dir_exists(trash_path); + chown_path(opts->user, trash_path); + if (rename(path, trash_path)) { + Errno("Could not move '%s' to '%s'", path, trash_path); + } + } + + free(path); + free(trash_path); + } + + Put("Done trashing!"); + Put("Once the drives fill up you may want to purge old data (-P)"); +} + +void mounts_purge(mounts_s *m) +{ + options_s *opts = m->opts; + drop_root(opts->user); + + Out("Purging all data from the following directories:"); + + int active_purgers = 0, max_purgers = 16; + if (opts->num_workers > max_purgers) + max_purgers = opts->num_workers; + + char *purge_path = NULL; + asprintf(&purge_path, "%s", opts->wd_base); + if (is_dir(purge_path)) { + Out(" %s", purge_path); + pid_t pid = fork(); + + if (pid == 0) { + ensure_dir_empty(purge_path); + free(purge_path); + exit(0); + + } else if (pid < 0) { + Errno("\nUnable to create cleaner process! :'-("); + } + active_purgers++; + } + free(purge_path); + + int cleaner_status = SUCCESS; + + for (int i = 0; i < m->count; i++) { + char *mp = m->mps[i]; + char *purge_path = NULL; + asprintf(&purge_path, "%s/%s", mp, _PATH_INSERT); + + if (is_dir(purge_path)) { + if (active_purgers+1 >= max_purgers) { + wait(&cleaner_status); + active_purgers--; + } + + // TODO: Use threading model same way as in init/init.c + pid_t pid = fork(); + if (pid == 0) { + Out(" %s", purge_path); + ensure_dir_empty(purge_path); + free(purge_path); + exit(0); + } else if (pid < 0) { + Errno("Unable to create cleaner process! :'-("); + } + active_purgers++; + } + free(purge_path); + } + + while (wait(&cleaner_status) > 0) + active_purgers--; + Put("\nCleaning done!"); +} + +void mounts_init(mounts_s *m) +{ + options_s *opts = m->opts; + char *wd_path = NULL; + asprintf(&wd_path, "%s/%s", opts->wd_base, opts->name); + ensure_dir_exists(wd_path); + chown_path(opts->user, opts->wd_base); + chown_path(opts->user, wd_path); + + if (chdir(wd_path)) { + Errno("Could not chdir into '%s'!", wd_path); + + } else { + Put("Chdir into '%s'", wd_path); + } + + free(wd_path); + + for (int i = 0; i < m->count; i++) { + char *mp = m->mps[i]; + char *path = NULL; + + // Create .ioreplay/ directory on MP + asprintf(&path, "%s/%s", mp, _PATH_INSERT); + ensure_dir_exists(path); + chown_path(m->opts->user, path); + free(path); + path = NULL; + + // Create .ioreplay/NAME directory on MP + asprintf(&path, "%s/%s/%s", mp, _PATH_INSERT, opts->name); + ensure_dir_exists(path); + chown_path(m->opts->user, path); + free(path); + } +} + +bool mounts_ignore_path(mounts_s *m, const char *path) +{ + // CentOS 7 specific, ignore temp namespace mounts! + char *pos = strstr(path, "/tmp/namespace-"); + if (pos == path) + return true; + + // iterate backwards through all mount points. + for (int i = m->ignore_count-1; i >= 0; --i) { + char *mountpoint = m->ignore_mps[i]; + pos = strstr(path, mountpoint); + // Ignore this path as it is in the ignore mp list + if (pos == path) + return true; + } + + return false; +} + +bool mounts_transform_path(mounts_s *m, const char *name, + char *path, char **path_r) +{ + char *tmp = NULL; +#ifdef DEBUG_TRANSFORM_PATH + char *original_path = path; +#endif + bool line_ok = true; + + // First figure out whether there are '..' in any paths. If so we have to + // tokenize the path and remove '..'. Example: + // 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; + } + + // Now heck whether the path is on a supported file system. If not, ignore! + if (mounts_ignore_path(m, path)) { + line_ok = false; + goto cleanup; + } + + // So the path is on a valid mount point! Now we need to insert + // .ioreplay/NAME to each mount point, e.g. /usr/local/.ioreplay/NAME/... + + // Iterate backwards through all mount points. + for (int i = m->count-1; i >= 0; --i) { + char *mountpoint = m->mps[i]; + int mp_len = m->lengths[i]; + + if (strncmp(path, mountpoint, mp_len) == 0) { + // Found a path to replace + // Now insert .ioreplay/NAME/ into the file path. + *path_r = Calloc(strlen(path) + strlen(name)+1 + + _PATH_INSERT_LEN+1, char); + + if (strcmp(mountpoint, "/") == 0) { + // Root path + strcpy(*path_r, _PATH_INSERT); + strcat(*path_r, name); + strcat(*path_r, path); + + } else { + strcpy(*path_r, mountpoint); + strcat(*path_r, _PATH_INSERT); + strcat(*path_r, name); + char *pos = path; + pos += mp_len * (int) sizeof(char); + strcat(*path_r, pos); + } + + goto cleanup; + } + } + + if (tmp) + free(tmp); + + return line_ok; + +cleanup: +#ifdef DEBUG_TRANSFORM_PATH + Debug("Transform path '%s' -> '%s' -> '%s'", original_path, path, *path_r); +#endif + if (tmp) + free(tmp); + + return line_ok; +} + +int mounts_get_mountnumber(mounts_s *m, const char *path) +{ + for (int i = m->count-1; i >= 0; --i) { + char *mountpoint = m->mps[i]; + int mp_len = m->lengths[i]; + + if (strncmp(path, mountpoint, mp_len) == 0) + return i; + } + + return 0; +} |
