commit - 927554b6d8fdd8c1e3783f422bf8ca53a16220d5
commit + 5a4f90240975e0a23735d9c1f935ff0215c4aaa0
blob - 4e026e1b6b1eb5b9c15191dc857f1480a90ff976
blob + 8303569b2df55dc82e79c7364b66c0a9bd3bd602
--- CMakeLists.txt
+++ CMakeLists.txt
project (unreliablefs
DESCRIPTION "A FUSE-based fault injection filesystem.")
-set(UNRELIABLEFS_SRC unreliablefs.c
+set(UNRELIABLEFS_SRC conf.c
+ unreliablefs.c
unreliablefs_errinj.c
unreliablefs_ops.c)
add_test(NAME pytest COMMAND pytest -c ${PROJECT_SOURCE_DIR}/tests/pytest.ini ${PROJECT_SOURCE_DIR}/tests/
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
-set(MANUAL_PAGES "unreliablefs.1")
+set(MANUAL_PAGES "unreliablefs.1;unreliablefs.conf.5")
add_custom_target(check DEPENDS check-mandoc)
add_custom_target(check-mandoc DEPENDS ${MANUAL_PAGES})
add_custom_command(TARGET check-mandoc
blob - e035e63cf465225688df2394fb4293fdff55ba09
blob + 7279944faea1fd360a270e4a5296980a9b5f2e45
--- README.md
+++ README.md
[](https://cirrus-ci.com/github/ligurio/unreliablefs)
-is a FUSE-based fault injection filesystem.
+is a FUSE-based fault injection filesystem that allows to change
+fault-injections in runtime.
+Supported fault injections are:
+
+- `errinj_noop` - replace file operation with no operation
+ (similar to [libeatmydata](https://github.com/stewartsmith/libeatmydata),
+ but applicable to any file operation).
+
### Building
Prerequisites:
### Using
```sh
-$ ./build/unreliablefs ~/Downloads/mnt/ -omodules=subdir,subdir=/tmp
-$ ls ~/Downloads/mnt/
-$ umount /tmp/unreliable
+$ mkdir /tmp/fs
+$ unreliablefs /tmp/fs -base_dir=/tmp -seed=1618680646
+$ cat << EOF > /tmp/fs/unreliablefs.conf
+[errinj_noop]
+op_regexp = .*
+path_regexp = .*
+probability = 30
+EOF
+$ ls -la
+$ umount /tmp/fs
```
blob - b758f3fa627bd4fdfc8673c01b1241a6a66afe25
blob + 449810c16ac5e06dcc96aa3e11aba1a6e4665eea
--- tests/test_unreliablefs.py
+++ tests/test_unreliablefs.py
mnt_dir = str(tmpdir.mkdir('mnt'))
src_dir = str(tmpdir.mkdir('src'))
- modules = "-omodules=subdir,subdir={}".format(src_dir)
- cmdline = base_cmdline + [ pjoin(basename, 'build/unreliablefs'), mnt_dir, modules]
+ options = "-basedir={}".format(src_dir)
+ cmdline = base_cmdline + [ pjoin(basename, 'build/unreliablefs'), mnt_dir, options ]
mount_process = subprocess.Popen(cmdline)
wait_for_mount(mount_process, mnt_dir)
blob - /dev/null
blob + b3c6dbcdf02d42abf6748168bd8a8ba57122e615 (mode 644)
--- /dev/null
+++ conf.c
+/* inih -- simple .INI file parser
+
+SPDX-License-Identifier: BSD-3-Clause
+
+Copyright (C) 2009-2020, Ben Hoyt
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "conf.h"
+
+#if !INI_USE_STACK
+#if INI_CUSTOM_ALLOCATOR
+#include <stddef.h>
+void* ini_malloc(size_t size);
+void ini_free(void* ptr);
+void* ini_realloc(void* ptr, size_t size);
+#else
+#include <stdlib.h>
+#define ini_malloc malloc
+#define ini_free free
+#define ini_realloc realloc
+#endif
+#endif
+
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Used by ini_parse_string() to keep track of string parsing state. */
+typedef struct {
+ const char* ptr;
+ size_t num_left;
+} ini_parse_string_ctx;
+
+/* Strip whitespace chars off end of given string, in place. Return s. */
+static char* rstrip(char* s)
+{
+ char* p = s + strlen(s);
+ while (p > s && isspace((unsigned char)(*--p)))
+ *p = '\0';
+ return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* lskip(const char* s)
+{
+ while (*s && isspace((unsigned char)(*s)))
+ s++;
+ return (char*)s;
+}
+
+/* Return pointer to first char (of chars) or inline comment in given string,
+ or pointer to NUL at end of string if neither found. Inline comment must
+ be prefixed by a whitespace character to register as a comment. */
+static char* find_chars_or_comment(const char* s, const char* chars)
+{
+#if INI_ALLOW_INLINE_COMMENTS
+ int was_space = 0;
+ while (*s && (!chars || !strchr(chars, *s)) &&
+ !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
+ was_space = isspace((unsigned char)(*s));
+ s++;
+ }
+#else
+ while (*s && (!chars || !strchr(chars, *s))) {
+ s++;
+ }
+#endif
+ return (char*)s;
+}
+
+/* Similar to strncpy, but ensures dest (size bytes) is
+ NUL-terminated, and doesn't pad with NULs. */
+static char* strncpy0(char* dest, const char* src, size_t size)
+{
+ /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
+ size_t i;
+ for (i = 0; i < size - 1 && src[i]; i++)
+ dest[i] = src[i];
+ dest[i] = '\0';
+ return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user)
+{
+ /* Uses a fair bit of stack (use heap instead if you need to) */
+#if INI_USE_STACK
+ char line[INI_MAX_LINE];
+ int max_line = INI_MAX_LINE;
+#else
+ char* line;
+ size_t max_line = INI_INITIAL_ALLOC;
+#endif
+#if INI_ALLOW_REALLOC && !INI_USE_STACK
+ char* new_line;
+ size_t offset;
+#endif
+ char section[MAX_SECTION] = "";
+ char prev_name[MAX_NAME] = "";
+
+ char* start;
+ char* end;
+ char* name;
+ char* value;
+ int lineno = 0;
+ int error = 0;
+
+#if !INI_USE_STACK
+ line = (char*)ini_malloc(INI_INITIAL_ALLOC);
+ if (!line) {
+ return -2;
+ }
+#endif
+
+#if INI_HANDLER_LINENO
+#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
+#else
+#define HANDLER(u, s, n, v) handler(u, s, n, v)
+#endif
+
+ /* Scan through stream line by line */
+ while (reader(line, (int)max_line, stream) != NULL) {
+#if INI_ALLOW_REALLOC && !INI_USE_STACK
+ offset = strlen(line);
+ while (offset == max_line - 1 && line[offset - 1] != '\n') {
+ max_line *= 2;
+ if (max_line > INI_MAX_LINE)
+ max_line = INI_MAX_LINE;
+ new_line = ini_realloc(line, max_line);
+ if (!new_line) {
+ ini_free(line);
+ return -2;
+ }
+ line = new_line;
+ if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
+ break;
+ if (max_line >= INI_MAX_LINE)
+ break;
+ offset += strlen(line + offset);
+ }
+#endif
+
+ lineno++;
+
+ start = line;
+#if INI_ALLOW_BOM
+ if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+ (unsigned char)start[1] == 0xBB &&
+ (unsigned char)start[2] == 0xBF) {
+ start += 3;
+ }
+#endif
+ start = lskip(rstrip(start));
+
+ if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
+ /* Start-of-line comment */
+ }
+#if INI_ALLOW_MULTILINE
+ else if (*prev_name && *start && start > line) {
+ /* Non-blank line with leading whitespace, treat as continuation
+ of previous name's value (as per Python configparser). */
+ if (!HANDLER(user, section, prev_name, start) && !error)
+ error = lineno;
+ }
+#endif
+ else if (*start == '[') {
+ /* A "[section]" line */
+ end = find_chars_or_comment(start + 1, "]");
+ if (*end == ']') {
+ *end = '\0';
+ strncpy0(section, start + 1, sizeof(section));
+ *prev_name = '\0';
+#if INI_CALL_HANDLER_ON_NEW_SECTION
+ if (!HANDLER(user, section, NULL, NULL) && !error)
+ error = lineno;
+#endif
+ }
+ else if (!error) {
+ /* No ']' found on section line */
+ error = lineno;
+ }
+ }
+ else if (*start) {
+ /* Not a comment, must be a name[=:]value pair */
+ end = find_chars_or_comment(start, "=:");
+ if (*end == '=' || *end == ':') {
+ *end = '\0';
+ name = rstrip(start);
+ value = end + 1;
+#if INI_ALLOW_INLINE_COMMENTS
+ end = find_chars_or_comment(value, NULL);
+ if (*end)
+ *end = '\0';
+#endif
+ value = lskip(value);
+ rstrip(value);
+
+ /* Valid name[=:]value pair found, call handler */
+ strncpy0(prev_name, name, sizeof(prev_name));
+ if (!HANDLER(user, section, name, value) && !error)
+ error = lineno;
+ }
+ else if (!error) {
+ /* No '=' or ':' found on name[=:]value line */
+#if INI_ALLOW_NO_VALUE
+ *end = '\0';
+ name = rstrip(start);
+ if (!HANDLER(user, section, name, NULL) && !error)
+ error = lineno;
+#else
+ error = lineno;
+#endif
+ }
+ }
+
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
+ }
+
+#if !INI_USE_STACK
+ ini_free(line);
+#endif
+
+ return error;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user)
+{
+ return ini_parse_stream((ini_reader)fgets, file, handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename, ini_handler handler, void* user)
+{
+ FILE* file;
+ int error;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return -1;
+ error = ini_parse_file(file, handler, user);
+ fclose(file);
+ return error;
+}
+
+/* An ini_reader function to read the next line from a string buffer. This
+ is the fgets() equivalent used by ini_parse_string(). */
+static char* ini_reader_string(char* str, int num, void* stream) {
+ ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
+ const char* ctx_ptr = ctx->ptr;
+ size_t ctx_num_left = ctx->num_left;
+ char* strp = str;
+ char c;
+
+ if (ctx_num_left == 0 || num < 2)
+ return NULL;
+
+ while (num > 1 && ctx_num_left != 0) {
+ c = *ctx_ptr++;
+ ctx_num_left--;
+ *strp++ = c;
+ if (c == '\n')
+ break;
+ num--;
+ }
+
+ *strp = '\0';
+ ctx->ptr = ctx_ptr;
+ ctx->num_left = ctx_num_left;
+ return str;
+}
+
+/* See documentation in header file. */
+int ini_parse_string(const char* string, ini_handler handler, void* user) {
+ ini_parse_string_ctx ctx;
+
+ ctx.ptr = string;
+ ctx.num_left = strlen(string);
+ return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
+ user);
+}
blob - cc0826a6abbfa403b1d8518fe2c2ec157978d90c
blob + 51fdc822a71ffae9f3a772beaa456a669f63adc8
--- unreliablefs.1
+++ unreliablefs.1
.Sh SYNOPSIS
.Nm
mountpoint
-.Op Fl h
+.Op Fl basedir Ar path
+.Op Fl seed Ar number
+.Op Fl hvdf
.Sh DESCRIPTION
The
.Nm
is a filesystem that allows to inject errors on file operations.
-It works as pass-through filesystem and redirects file operations to a file
-objects on real filesystem.
+Without configuration it works as pass-through filesystem and redirects file
+operations to a file objects on a real filesystem.
.Pp
.Nm
uses Filesystem in Userspace (FUSE) that allows easy setup without requiring a
To mount filesystem it is required to specify mountpoint and after mount it
will contain the same file tree as a root filesystem.
.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl basedir Ar path
+Specify path to a directory that should be mount.
+.It Fl seed Ar number
+Specify a seed.
+.It Fl f
+Do not daemonize.
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl v
+Show version.
+.It Fl h
+Show usage.
+.El
+.Pp
Supported file operations are:
.Xr access 2 ,
.Xr chmod 2 ,
.Sh EXAMPLES
.Bd -literal
-$ mkdir /tmp/unreliable
-$ unreliablefs /tmp/unreliable
-$ umount /tmp/unreliable
+$ mkdir /tmp/fs
+$ unreliablefs /tmp/fs -basedir=/tmp -seed=1618680646
+$ cat << EOF > /tmp/fs/unreliablefs.conf
+[errinj_noop]
+op_regexp = .*
+path_regexp = .*
+probability = 30
+EOF
+$ ls -la
+$ umount /tmp/fs
.Ed
.Sh SEE ALSO
.Xr fusermount 1 ,
.Xr errno 2 ,
.Xr fuse 4 ,
-.Xr re_format 7 ,
+.Xr fuse 8 ,
+.Xr unreliablefs.conf 5 ,
.Xr mount.fuse 8
.Sh AUTHORS
.An -nosplit
blob - /dev/null
blob + 78015d141e6878398e001790f28c480d66679edd (mode 644)
--- /dev/null
+++ conf.h
+/* inih -- simple .INI file parser
+
+SPDX-License-Identifier: BSD-3-Clause
+
+Copyright (C) 2009-2020, Ben Hoyt
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#ifndef INI_H
+#define INI_H
+
+/* Make this header file easier to include in C++ code */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* Nonzero if ini_handler callback should accept lineno parameter. */
+#ifndef INI_HANDLER_LINENO
+#define INI_HANDLER_LINENO 0
+#endif
+
+/* Typedef for prototype of handler function. */
+#if INI_HANDLER_LINENO
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value,
+ int lineno);
+#else
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value);
+#endif
+
+/* Typedef for prototype of fgets-style reader function. */
+typedef char* (*ini_reader)(char* str, int num, void* stream);
+
+/* Parse given INI-style file. May have [section]s, name=value pairs
+ (whitespace stripped), and comments starting with ';' (semicolon). Section
+ is "" if name=value pair parsed before any section heading. name:value
+ pairs are also supported as a concession to Python's configparser.
+
+ For each name=value pair parsed, call handler function with given user
+ pointer as well as section, name, and value (data only valid for duration
+ of handler call). Handler should return nonzero on success, zero on error.
+
+ Returns 0 on success, line number of first error on parse error (doesn't
+ stop on first error), -1 on file open error, or -2 on memory allocation
+ error (only when INI_USE_STACK is zero).
+*/
+int ini_parse(const char* filename, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
+ close the file when it's finished -- the caller must do that. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes an ini_reader function pointer instead of
+ filename. Used for implementing custom or string-based I/O (see also
+ ini_parse_string). */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user);
+
+/* Same as ini_parse(), but takes a zero-terminated string with the INI data
+instead of a file. Useful for parsing INI data from a network socket or
+already in memory. */
+int ini_parse_string(const char* string, ini_handler handler, void* user);
+
+/* Nonzero to allow multi-line value parsing, in the style of Python's
+ configparser. If allowed, ini_parse() will call the handler with the same
+ name for each subsequent line parsed. */
+#ifndef INI_ALLOW_MULTILINE
+#define INI_ALLOW_MULTILINE 1
+#endif
+
+/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
+ the file. See https://github.com/benhoyt/inih/issues/21 */
+#ifndef INI_ALLOW_BOM
+#define INI_ALLOW_BOM 1
+#endif
+
+/* Chars that begin a start-of-line comment. Per Python configparser, allow
+ both ; and # comments at the start of a line by default. */
+#ifndef INI_START_COMMENT_PREFIXES
+#define INI_START_COMMENT_PREFIXES ";#"
+#endif
+
+/* Nonzero to allow inline comments (with valid inline comment characters
+ specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
+ Python 3.2+ configparser behaviour. */
+#ifndef INI_ALLOW_INLINE_COMMENTS
+#define INI_ALLOW_INLINE_COMMENTS 1
+#endif
+#ifndef INI_INLINE_COMMENT_PREFIXES
+#define INI_INLINE_COMMENT_PREFIXES ";"
+#endif
+
+/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
+#ifndef INI_USE_STACK
+#define INI_USE_STACK 1
+#endif
+
+/* Maximum line length for any line in INI file (stack or heap). Note that
+ this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
+#ifndef INI_MAX_LINE
+#define INI_MAX_LINE 200
+#endif
+
+/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
+ fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
+ zero. */
+#ifndef INI_ALLOW_REALLOC
+#define INI_ALLOW_REALLOC 0
+#endif
+
+/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
+ is zero. */
+#ifndef INI_INITIAL_ALLOC
+#define INI_INITIAL_ALLOC 200
+#endif
+
+/* Stop parsing on first error (default is to keep parsing). */
+#ifndef INI_STOP_ON_FIRST_ERROR
+#define INI_STOP_ON_FIRST_ERROR 0
+#endif
+
+/* Nonzero to call the handler at the start of each new section (with
+ name and value NULL). Default is to only call the handler on
+ each name=value pair. */
+#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
+#define INI_CALL_HANDLER_ON_NEW_SECTION 0
+#endif
+
+/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
+ call the handler with value NULL in this case. Default is to treat
+ no-value lines as an error. */
+#ifndef INI_ALLOW_NO_VALUE
+#define INI_ALLOW_NO_VALUE 0
+#endif
+
+/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
+ allocation functions (INI_USE_STACK must also be 0). These functions must
+ have the same signatures as malloc/free/realloc and behave in a similar
+ way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
+#ifndef INI_CUSTOM_ALLOCATOR
+#define INI_CUSTOM_ALLOCATOR 0
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INI_H */
blob - 8d5d93bb0ed6d1e0e4293a0a3ed38987a23ada8f
blob + c64750ff018d6a77dfceaf9bf7b20d0a9f53f40b
--- unreliablefs.c
+++ unreliablefs.c
#define FUSE_USE_VERSION 29
#include <errno.h>
+#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <fuse.h>
+
#include "unreliablefs_ops.h"
+#include "unreliablefs.h"
+extern struct err_inj_q *config_init(const char* conf_path);
+extern void config_delete(struct err_inj_q *config);
+
static struct fuse_operations unreliable_ops = {
.getattr = unreliable_getattr,
.readlink = unreliable_readlink,
#endif /* HAVE_UTIMENSAT */
};
+enum {
+ KEY_HELP,
+ KEY_VERSION,
+ KEY_DEBUG,
+};
+
+#define UNRELIABLEFS_OPT(t, p, v) { t, offsetof(struct unreliablefs_config, p), v }
+#define UNRELIABLEFS_VERSION "0.1"
+
+static struct fuse_opt unreliablefs_opts[] = {
+ UNRELIABLEFS_OPT("-seed=%u", seed, 0),
+ UNRELIABLEFS_OPT("-basedir=%s", basedir, 0),
+
+ FUSE_OPT_KEY("-d", KEY_DEBUG),
+ FUSE_OPT_KEY("-V", KEY_VERSION),
+ FUSE_OPT_KEY("-v", KEY_VERSION),
+ FUSE_OPT_KEY("--version", KEY_VERSION),
+ FUSE_OPT_KEY("-h", KEY_HELP),
+ FUSE_OPT_KEY("--help", KEY_HELP),
+ FUSE_OPT_KEY("subdir", FUSE_OPT_KEY_DISCARD),
+ FUSE_OPT_KEY("modules=", FUSE_OPT_KEY_DISCARD),
+ FUSE_OPT_END
+};
+
+static int unreliablefs_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs)
+{
+ switch (key) {
+ case KEY_HELP:
+ fprintf(stderr,
+ "usage: unreliablefs mountpoint [options]\n\n"
+ "general options:\n"
+ " -h --help print help\n"
+ " -v --version print version\n"
+ " -d enable debug output (implies -f)\n"
+ " -f foreground operation\n\n"
+ "unreliablefs options:\n"
+ " -seed=NUM random seed\n"
+ " -basedir=STRING directory to mount\n\n");
+ exit(1);
+
+ case KEY_VERSION:
+ fprintf(stderr, "unreliablefs version %s\n", UNRELIABLEFS_VERSION);
+ fuse_opt_add_arg(outargs, "--version");
+ fuse_main(outargs->argc, outargs->argv, &unreliable_ops, NULL);
+ exit(1);
+ }
+ return 1;
+}
+
+int is_dir(const char *path) {
+ struct stat statbuf;
+ if (stat(path, &statbuf) != 0) {
+ return 0;
+ }
+
+ return S_ISDIR(statbuf.st_mode);
+}
+
int main(int argc, char *argv[])
{
-#if defined(DEBUG)
- int fuse_argc = 3;
- char *fuse_argv[fuse_argc];
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ memset(&conf, 0, sizeof(conf));
+ conf.seed = time(0);
+ conf.basedir = "/";
+ fuse_opt_parse(&args, &conf, unreliablefs_opts, unreliablefs_opt_proc);
+ srand(conf.seed);
+ fprintf(stdout, "random seed = %d\n", conf.seed);
- if (argc != 2) {
- fprintf(stderr, "Usage: %s MOUNTPOINT\n", argv[0]);
- return EXIT_FAILURE;
+ if (is_dir(conf.basedir) == 0) {
+ fprintf(stderr, "basedir ('%s') is not a directory\n", conf.basedir);
+ fuse_opt_free_args(&args);
+ return EXIT_FAILURE;
}
+ char subdir_option[PATH_MAX];
+ sprintf(subdir_option, "-omodules=subdir,subdir=%s", conf.basedir);
+ fuse_opt_add_arg(&args, subdir_option);
+ /* build config_path */
+ char *real_path = realpath(conf.basedir, NULL);
+ if (!real_path) {
+ perror("realpath");
+ fuse_opt_free_args(&args);
+ return EXIT_FAILURE;
+ }
+ conf.basedir = real_path;
+ size_t sz = strlen(DEFAULT_CONF_NAME) + strlen(conf.basedir) + 2;
+ conf.config_path = malloc(sz);
+ if (!conf.config_path) {
+ perror("malloc");
+ fuse_opt_free_args(&args);
+ return EXIT_FAILURE;
+ }
+ /* read configuration file on start */
+ snprintf(conf.config_path, sz, "%s/%s", conf.basedir, DEFAULT_CONF_NAME);
+ conf.errors = config_init(conf.config_path);
+ if (!conf.errors) {
+ fprintf(stdout, "error injections are not configured!\n");
+ }
+ if (pthread_mutex_init(&conf.mutex, NULL) != 0) {
+ fuse_opt_free_args(&args);
+ perror("pthread_mutex_init");
+ return EXIT_FAILURE;
+ }
- fuse_argv[0] = argv[0];
- fuse_argv[1] = argv[1];
- #if FUSE_USE_VERSION < 30
- fuse_argv[2] = "-ononempty,suid,dev,allow_other,default_permissions";
- #else
- fuse_argv[2] = "-osuid,dev,allow_other,default_permissions";
- #endif
- fuse_argv[3] = NULL;
+ fprintf(stdout, "starting FUSE filesystem unreliablefs\n");
+ int ret = fuse_main(args.argc, args.argv, &unreliable_ops, NULL);
- fprintf(stdout, "Starting FUSE filesystem\n");
- return fuse_main(fuse_argc, fuse_argv, &unreliable_ops, NULL);
-#else
- fprintf(stdout, "Starting FUSE filesystem\n");
- return fuse_main(argc, argv, &unreliable_ops, NULL);
-#endif /* DEBUG */
+ /* cleanup */
+ fuse_opt_free_args(&args);
+ config_delete(conf.errors);
+ if (conf.config_path)
+ free(conf.config_path);
+ if (!ret) {
+ fprintf(stdout, "random seed = %d\n", conf.seed);
+ }
+
+ return ret;
}
blob - 81ead53e35061f31ac181a6bc17c9241bca107f8
blob + ca7bc1fd4dd53ea18989ab28d6cbd9f2b3830f8d
--- unreliablefs_errinj.c
+++ unreliablefs_errinj.c
#define FUSE_USE_VERSION 29
-#include <errno.h>
+#include <fuse.h>
+#include <libgen.h> /* basename() and dirname() */
+#include <regex.h>
#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
-#if !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__APPLE__)
-#define MAX_ERR EXFULL
-#else
-#define MAX_ERR ELAST
-#endif /* __OpenBSD__ */
-#define MIN_ERR E2BIG
+#include <sys/queue.h>
-#define PROBABILITY 5
+#include "conf.h"
+#include "unreliablefs.h"
+#include "unreliablefs_errinj.h"
static int rand_range(int min_n, int max_n)
{
int error_inject(const char* path, char* operation)
{
-#if defined(DEBUG)
- int err_no = 0;
- int p = rand_range(0, 100);
- if ((p >= 0) && (p <= PROBABILITY)) {
- err_no = rand_range(MIN_ERR, MAX_ERR);
+ int rc = -0;
+ struct errinj_conf *err;
+ /* read configuration file on change */
+ pthread_mutex_lock(&conf.mutex);
+ if (strcmp(path, conf.config_path) == 0) {
+ config_delete(conf.errors);
+ conf.errors = config_init(path);
}
+ if (!conf.errors) {
+ goto cleanup;
+ }
- return -err_no;
-#else
- return 0;
-#endif /* DEBUG */
+ /* apply error injections defined in configuration one by one */
+ TAILQ_FOREACH(err, conf.errors, entries) {
+ if (is_regex_matched(err->path_regexp, path) != 0) {
+ fprintf(stderr, "errinj '%s' skipped: path_regexp (%s) is not matched\n",
+ errinj_name[err->type], err->path_regexp);
+ continue;
+ }
+ if (is_regex_matched(err->op_regexp, operation) != 0) {
+ fprintf(stderr, "errinj '%s' skipped: op_regexp (%s) is not matched\n",
+ errinj_name[err->type], err->op_regexp);
+ continue;
+ }
+ int p = rand_range(MIN_PROBABLITY, MAX_PROBABLITY);
+ if (!((p >= 0) && (p <= err->probability))) {
+ fprintf(stderr, "errinj '%s' skipped: probability (%d) is not matched\n",
+ errinj_name[err->type], err->probability);
+ continue;
+ }
+ switch (err->type) {
+ case ERRINJ_NOOP:
+ fprintf(stderr, "%s triggered on operation '%s', %s\n",
+ errinj_name[err->type], operation, path);
+ rc = 0;
+ break;
+ }
+ }
+
+cleanup:
+ pthread_mutex_unlock(&conf.mutex);
+ return rc;
}
+
+static errinj_type errinj_type_by_name(const char *name)
+{
+ int idx = 0;
+ int n_elem = sizeof(errinj_name)/sizeof(errinj_name[0]);
+ for (int i = 0; i < n_elem; i++) {
+ if (strcmp(errinj_name[i], name) == 0)
+ idx = i;
+ }
+
+ return (errinj_type)idx;
+}
+
+struct err_inj_q *config_init(const char* conf_path) {
+ fprintf(stdout, "read configuration %s\n", conf_path);
+ struct err_inj_q *errors = calloc(1, sizeof(struct err_inj_q));
+ if (!errors) {
+ perror("calloc");
+ return NULL;
+ }
+ TAILQ_INIT(errors); /* initialize queue */
+ if (access(conf_path, F_OK ) == 0) {
+ if (ini_parse(conf_path, conf_option_handler, errors) < 0) {
+ fprintf(stderr, "can't load '%s'\n", conf_path);
+ return NULL;
+ }
+ }
+
+ return errors;
+}
+
+void config_delete(struct err_inj_q *errors) {
+ if (!errors) {
+ return;
+ }
+ errinj_conf *err;
+ /* delete configuration of error injections */
+ while ((err= TAILQ_FIRST(errors))) {
+ if (err->path_regexp)
+ free((char*)err->path_regexp);
+ if (err->op_regexp)
+ free((char*)err->op_regexp);
+ TAILQ_REMOVE(errors, err, entries);
+ free(err);
+ }
+ free(errors);
+}
+
+int conf_option_handler(void* cfg, const char* section,
+ const char* key, const char* value)
+{
+ errinj_conf *err = NULL;
+ errinj_type cur_type = errinj_type_by_name(section);
+ errinj_conf *np;
+ int is_errinj_found = 0;
+ TAILQ_FOREACH(np, (struct err_inj_q *)cfg, entries) {
+ if (np->type == cur_type) {
+ err = np;
+ is_errinj_found = 1;
+ break;
+ }
+ }
+ if (!err) {
+ if ((err = calloc(1, sizeof(struct errinj_conf))) == NULL) {
+ perror("calloc");
+ return -1;
+ }
+ err->type = cur_type;
+ }
+
+ if (is_errinj_found != 1) {
+ TAILQ_INSERT_TAIL((struct err_inj_q *)cfg, err, entries);
+ fprintf(stdout, "enabled error injection %s\n", section);
+ }
+ fprintf(stdout, "[%s] %s = %s\n", section, key, value);
+ if (strcmp(key, "path_regexp") == 0) {
+ if (err->path_regexp)
+ free(err->path_regexp);
+ err->path_regexp = strdup(value);
+ } else if (strcmp(key, "op_regexp") == 0) {
+ if (err->op_regexp)
+ free(err->op_regexp);
+ err->op_regexp = strdup(value);
+ } else if (strcmp(key, "probability") == 0) {
+ err->probability = atoi(value);
+ } else {
+ fprintf(stderr, "unknown option '%s' in configuration file\n", key);
+ return 0;
+ }
+
+ return 1;
+}
+
+int is_regex_matched(const char *regex, const char *string) {
+ if (!regex || !string)
+ return 0;
+ regex_t reg;
+ regmatch_t match[1];
+ int rc = regcomp(®, regex, REG_ICASE | REG_EXTENDED);
+ if (rc != 0) {
+ perror("regcomp");
+ regfree(®);
+ return rc;
+ }
+ rc = regexec(®, string, 1, match, 0);
+ if (rc != 0) {
+ perror("regexec");
+ }
+ regfree(®);
+ return rc;
+}
blob - 0fcd5ef17344fb7ceb8744475e73e40ea1b54457
blob + 8f3a081777a733e4d6dd713f3d88eb1e31f5cc02
--- unreliablefs_errinj.h
+++ unreliablefs_errinj.h
#ifndef ERRINJ_HH
#define ERRINJ_HH
+#include <signal.h>
+
+#define MAX_ERRINJ_NAME_LENGTH 20
+
+#if !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__APPLE__)
+#define MAX_ERRNO EXFULL
+#else
+#define MAX_ERRNO ELAST
+#endif /* __OpenBSD__ */
+#define MIN_ERRNO E2BIG
+
+#define MIN_PROBABLITY 0
+#define MAX_PROBABLITY 100
+
int error_inject(const char* path, char* operation);
+struct err_inj_q *config_init(const char* conf_path);
+void config_delete(struct err_inj_q *config);
+int conf_option_handler(void* cfg, const char* section,
+ const char* name, const char* value);
+int is_regex_matched(const char *regex, const char *string);
+const char *errinj_name[] =
+{
+ "errinj_noop",
+};
+
+typedef enum {
+ ERRINJ_NOOP,
+} errinj_type;
+
+typedef struct errinj_conf errinj_conf;
+
+struct errinj_conf {
+ char *err_injection_name;
+ char *op_regexp;
+ char *path_regexp;
+ int probability;
+ errinj_type type;
+
+ TAILQ_ENTRY(errinj_conf) entries;
+};
+
+TAILQ_HEAD(err_inj_q, errinj_conf);
+
#endif /* ERRINJ_HH */
blob - d69f69870fab9e88a902ea7c428d8ebf01f3543e
blob + d9e2c0ff05d0733be8dd43c85f57154ef9ae5f96
--- unreliablefs_ops.c
+++ unreliablefs_ops.c
#define _XOPEN_SOURCE 700
#endif
-#include "unreliablefs_errinj.h"
#include "unreliablefs_ops.h"
+extern int error_inject(const char* path, char* operation);
+
int unreliable_lstat(const char *path, struct stat *buf)
{
int ret = error_inject(path, "lstat");
blob - /dev/null
blob + a27f32bbc1825578e040a54c9326b06fea056b87 (mode 644)
--- /dev/null
+++ unreliablefs.conf.5
+.\" Copyright (c) 2021 Sergey Bronnikov
+.\"
+.Dd $Mdocdate: April 15 2021 $
+.Dt UNRELIABLEFS.CONF 5
+.Os
+.Sh NAME
+.Nm unreliablefs.conf
+.Nd format of the configuration file used by
+.Xr unreliablefs 1
+.Sh DESCRIPTION
+The configuration file format is quite simple.
+Sections are delimited by square brackets:
+.Pp
+.Rs
+[Section]
+.Re
+.Pp
+And options within brackets sections are simple key value pairs:
+.Pp
+.Rs
+Option = Value
+.Re
+.Sh OPTIONS
+Per-fault-injection customizable variables are specified within sections
+with section names matching the fault-injection name.
+.Pp
+Supported fault injections are:
+.Bl -tag -width Ds
+.It Cm errinj_noop
+File operation replaced with no-op.
+.El
+.Pp
+The options are:
+.Bl -tag -width Ds
+.It Cm op_regexp
+Sets the regular expression that matches file operation for what fault injection is applicable.
+Option uses format of regular expressions described in
+.Xr re_format 7 .
+POSIX Extended Regular Expression syntax is supported and regular expressions do not differentiate case.
+.It Cm path_regexp
+Sets the regular expression that matches paths where fault injection is applicable.
+Option uses format of regular expressions described in
+.Xr re_format 7 .
+POSIX Extended Regular Expression syntax is supported and regular expressions do not differentiate case.
+.It Cm probability
+Sets the probability in percents.
+Probability equal to 0 means that error injection will never happen.
+Probability equal to 100 means that error injection will happen on each file operation.
+.El
+.Sh EXAMPLES
+.Bd -literal
+
+[errinj_noop]
+path_regexp = .*
+op_regexp = .*
+probability = 70
+
+.Ed
+.Sh SEE ALSO
+.Xr unreliablefs 1 ,
+.Xr errno 2 ,
+.Xr syslog 3 ,
+.Xr re_format 7
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr unreliablefs 1
+utility was written by
+.An Sergey
+.An Bronnikov .
+.\" .Sh HISTORY
+.\" .Sh BUGS
+.\" .Sh CAVEATS
blob - /dev/null
blob + 90b3eb1b21a0708a0631e25873275fe3ecdd591f (mode 644)
--- /dev/null
+++ unreliablefs.h
+#ifndef UNRELIABLEFS_HH
+#define UNRELIABLEFS_HH
+
+#include <limits.h> /* PATH_MAX */
+#include <pthread.h>
+
+#define DEFAULT_CONF_NAME "unreliablefs.conf"
+
+typedef struct unreliablefs_config {
+ struct err_inj_q *errors;
+ char *basedir;
+ char *config_path;
+ unsigned int seed;
+ unsigned int debug;
+ pthread_mutex_t mutex;
+} unreliablefs_config;
+
+struct unreliablefs_config conf;
+
+#endif /* UNRELIABLEFS_HH */