author | Alberto Bertogli
<albertito@blitiri.com.ar> 2009-03-15 04:39:57 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2009-03-20 22:45:32 UTC |
.gitignore | +7 | -0 |
LICENSE | +30 | -0 |
Makefile | +30 | -0 |
README | +44 | -0 |
bindings/python2/LICENSE | +30 | -0 |
bindings/python2/fiu.py | +75 | -0 |
bindings/python2/fiu_ll.c | +191 | -0 |
bindings/python2/setup.py | +17 | -0 |
doc/guide.rst | +171 | -0 |
libfiu/Makefile | +77 | -0 |
libfiu/fiu-control.h | +59 | -0 |
libfiu/fiu-local.h | +37 | -0 |
libfiu/fiu.c | +387 | -0 |
libfiu/fiu.h | +71 | -0 |
libfiu/libfiu.3 | +170 | -0 |
libfiu/libfiu.skel.pc | +12 | -0 |
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0c7c3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bindings/python2/build +bindings/python2/*.pyc +bindings/python2/*.pyo +libfiu/*.o +libfiu/libfiu.a +libfiu/libfiu.pc +libfiu/libfiu.so diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edb80f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ + +I don't like licenses, because I don't like having to worry about all this +legal stuff just for a simple piece of software I don't really mind anyone +using. But I also believe that it's important that people share and give back; +so I'm placing this work under the following license. + + +BOLA - Buena Onda License Agreement (v1.0) +------------------------------------------ + +This work is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of +this work. + +To all effects and purposes, this work is to be considered Public Domain. + + +However, if you want to be "buena onda", you should: + +1. Not take credit for it, and give proper recognition to the authors. +2. Share your modifications, so everybody benefits from them. +4. Do something nice for the authors. +5. Help someone who needs it: sign up for some volunteer work or help your + neighbour paint the house. +6. Don't waste. Anything, but specially energy that comes from natural + non-renewable resources. Extra points if you discover or invent something + to replace them. +7. Be tolerant. Everything that's good in nature comes from cooperation. + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a05295c --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ + +all: default + +default: libfiu utils + +libfiu: + $(MAKE) -C libfiu + +install: + $(MAKE) -C libfiu install + + +python2: + cd bindings/python2 && python setup.py build + +python2_install: + cd bindings/python2 && python setup.py install + +python2_clean: + cd bindings/python2 && rm -rf build/ + + +clean: python2_clean + $(MAKE) -C libfiu clean + + +.PHONY: default all clean libfiu utils \ + python2 python2_install python2_clean + + diff --git a/README b/README new file mode 100644 index 0000000..8ba4ef7 --- /dev/null +++ b/README @@ -0,0 +1,44 @@ + +libfiu - Fault injection in userspace +------------------------------------- + +libfiu is a C library for fault injection. It provides functions to mark +"points of failure" inside your code (the "core API"), and functions to +enable/disable the failure of those points (the "control API"). It's in the +public domain, see the LICENSE file for more information. + +The core API is used inside the code wanting to perform failure injection on. +The control API is used inside the testing code, in order to control the +injection of failures. + +Python bindings are available in the "bindings" directory. + + +Documentation +------------- + +You can find the user guide in the "doc" directory, and a manpage in the +"libfiu" directory. The manpage will be installed along the library. + +Python bindings have embedded documentation, although it's not as complete. + + +Building and installing +----------------------- + +Running "make" (or "gmake") should be enough for building, and "make install" +for installing. By default it installs into /usr/local, but you can provide an +alternative prefix by running "make PREFIX=/my/prefix install". + +To build the Python bindings, use "make python2"; to install them you can run +"make python2_install". + + +Where to report bugs +-------------------- + +If you want to report bugs, or have any questions or comments, just let me +know at albertito@blitiri.com.ar. For more information about the library, you +can go to http://blitiri.com.ar/p/libfiu. + + diff --git a/bindings/python2/LICENSE b/bindings/python2/LICENSE new file mode 100644 index 0000000..edb80f0 --- /dev/null +++ b/bindings/python2/LICENSE @@ -0,0 +1,30 @@ + +I don't like licenses, because I don't like having to worry about all this +legal stuff just for a simple piece of software I don't really mind anyone +using. But I also believe that it's important that people share and give back; +so I'm placing this work under the following license. + + +BOLA - Buena Onda License Agreement (v1.0) +------------------------------------------ + +This work is provided 'as-is', without any express or implied warranty. In no +event will the authors be held liable for any damages arising from the use of +this work. + +To all effects and purposes, this work is to be considered Public Domain. + + +However, if you want to be "buena onda", you should: + +1. Not take credit for it, and give proper recognition to the authors. +2. Share your modifications, so everybody benefits from them. +4. Do something nice for the authors. +5. Help someone who needs it: sign up for some volunteer work or help your + neighbour paint the house. +6. Don't waste. Anything, but specially energy that comes from natural + non-renewable resources. Extra points if you discover or invent something + to replace them. +7. Be tolerant. Everything that's good in nature comes from cooperation. + + diff --git a/bindings/python2/fiu.py b/bindings/python2/fiu.py new file mode 100644 index 0000000..f4e6ece --- /dev/null +++ b/bindings/python2/fiu.py @@ -0,0 +1,75 @@ + +""" +libfiu python wrapper + +This module is a wrapper for the libfiu, the fault injection C library. + +It provides an almost one-to-one mapping of the libfiu functions, although its +primary use is to be able to test C code from within Python. + +For fault injection in Python, a native library would be more suitable. + +See libfiu's manpage for more detailed documentation. +""" + +import fiu_ll as _ll + + +def fail(name): + "Returns the failure status of the given point of failure." + return _ll.fail(name) + +def failinfo(name): + """Returns the information associated with the last failure. Use with + care, can be fatal if the point of failure was not enabled via + Python.""" + return _ll.failinfo() + + +# To be sure failinfo doesn't dissapear from under our feet, we keep a +# name -> failinfo table. See fiu_ll's comments for more details. +_fi_table = {} + +def enable(name, failnum = 1, failinfo = None, flags = 0): + "Enables the given point of failure." + _fi_table[name] = failnum + r = _ll.enable(name, failnum, failinfo, flags) + if r != 0: + del _fi_table[name] + raise RuntimeError, r + +def enable_random(name, probability, failnum = 1, failinfo = None, flags = 0): + "Enables the given point of failure, with the given probability." + _fi_table[name] = failnum + r = _ll.enable_random(name, failnum, failinfo, flags, probability) + if r != 0: + del _fi_table[name] + raise RuntimeError, r + +def enable_external(name, cb, failnum = 1, flags = 0): + """Enables the given point of failure, leaving the decision whether to + fail or not to the given external function, which should return 0 if + it is not to fail, or 1 otherwise. + + The cb parameter is a Python function that takes three parameters, + name, failnum and flags, with the same values that we receive. + + For technical limitations, enable_external() cannot take + failinfo.""" + # in this case, use the table to prevent the function from + # dissapearing + _fi_table[name] = cb + r = _ll.enable_external(name, failnum, flags, cb) + if r != 0: + raise RuntimeError, r + +def disable(name): + """Disables the given point of failure, undoing the actions of the + enable*() functions.""" + if name in _fi_table: + del _fi_table[name] + r = _ll.disable(name) + if r != 0: + raise RuntimeError, r + + diff --git a/bindings/python2/fiu_ll.c b/bindings/python2/fiu_ll.c new file mode 100644 index 0000000..8afb439 --- /dev/null +++ b/bindings/python2/fiu_ll.c @@ -0,0 +1,191 @@ + +/* + * Python bindings for libfiu + * Alberto Bertogli (albertito@blitiri.com.ar) + * + * This is the low-level module, used by the python one to construct + * friendlier objects. + */ + +#include <Python.h> + +/* Unconditionally enable fiu, otherwise we get fake headers */ +#define FIU_ENABLE 1 + +#include <fiu.h> +#include <fiu-control.h> + + +static PyObject *fail(PyObject *self, PyObject *args) +{ + char *name; + PyObject *rv, *err; + + if (!PyArg_ParseTuple(args, "s:fail", &name)) + return NULL; + + rv = PyLong_FromLong(fiu_fail(name)); + err = PyErr_Occurred(); + + if (rv == NULL || err != NULL) { + Py_XDECREF(rv); + return NULL; + } + + return rv; +} + +static PyObject *failinfo(PyObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":failinfo")) + return NULL; + + /* We assume failinfo is a python object; but the caller must be + * careful because if it's not, it can get into trouble. + * Note that we DO NOT TOUCH THE RC OF THE OBJECT. It's entirely up to + * the caller to make sure it's still alive. */ + return (PyObject *) fiu_failinfo(); +} + +static PyObject *enable(PyObject *self, PyObject *args) +{ + char *name; + int failnum; + PyObject *failinfo; + unsigned int flags; + + if (!PyArg_ParseTuple(args, "siOI:enable", &name, &failnum, &failinfo, + &flags)) + return NULL; + + /* See failinfo()'s comment regarding failinfo's RC */ + return PyLong_FromLong(fiu_enable(name, failnum, failinfo, flags)); +} + +static PyObject *enable_random(PyObject *self, PyObject *args) +{ + char *name; + int failnum; + PyObject *failinfo; + unsigned int flags; + float probability; + + if (!PyArg_ParseTuple(args, "siOIf:enable_random", &name, &failnum, + &failinfo, &flags, &probability)) + return NULL; + + /* See failinfo()'s comment regarding failinfo's RC */ + return PyLong_FromLong(fiu_enable_random(name, failnum, failinfo, + flags, probability)); +} + + +static int external_callback(const char *name, int *failnum, void **failinfo, + unsigned int *flags) +{ + int rv; + PyObject *cbrv; + PyObject *args; + PyGILState_STATE gil_state; + + /* We need to protect ourselves from the following case: + * - fiu.enable_callback('x', cb) (where cb is obviously a Python + * function) + * - Later on, call a function p1() inside a python C module, that + * runs a C function c1() inside + * Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS + * - c1() calls fiu_fail("x") + * - fiu_fail("x") calls external_callback(), and it should run cb() + * - BUT! It can't run cb(), because it's inside + * Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS so it's not safe to + * execute any Python code! + * + * The solution is to ensure we're safe to run Python code using + * PyGILState_Ensure()/PyGILState_Release(). + */ + + gil_state = PyGILState_Ensure(); + args = Py_BuildValue("(siI)", name, *failnum, *flags); + if (args == NULL) { + PyGILState_Release(gil_state); + return 0; + } + + cbrv = PyEval_CallObject(*failinfo, args); + Py_DECREF(args); + + if (cbrv == NULL) { + PyGILState_Release(gil_state); + return 0; + } + + /* If PyInt_AsLong() causes an error, it will be handled by the + * PyErr_Occurred() check in fail(), so we don't need to worry about + * it now. */ + rv = PyInt_AsLong(cbrv); + Py_DECREF(cbrv); + + PyGILState_Release(gil_state); + + return rv; +} + +static PyObject *enable_external(PyObject *self, PyObject *args) +{ + char *name; + int failnum; + unsigned int flags; + PyObject *py_external_cb; + + if (!PyArg_ParseTuple(args, "siIO:enable_random", &name, &failnum, + &flags, &py_external_cb)) + return NULL; + + if (!PyCallable_Check(py_external_cb)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return NULL; + } + + /* We use failinfo to store Python's callback function. It'd be nice + * if we could keep both, but it's not easy without keeping state + * inside the C module. + * + * Similar to the way failinfo is handled, we DO NOT TOUCH THE RC OF + * THE EXTERNAL CALLBACK, assuming the caller will take care of making + * sure it doesn't dissapear from beneath us. */ + return PyLong_FromLong(fiu_enable_external(name, failnum, + py_external_cb, flags, external_callback)); +} + +static PyObject *disable(PyObject *self, PyObject *args) +{ + char *name; + + if (!PyArg_ParseTuple(args, "s:fail", &name)) + return NULL; + + return PyLong_FromLong(fiu_disable(name)); +} + + +static PyMethodDef fiu_functions[] = { + { "fail", (PyCFunction) fail, METH_VARARGS, NULL }, + { "failinfo", (PyCFunction) failinfo, METH_VARARGS, NULL }, + { "enable", (PyCFunction) enable, METH_VARARGS, NULL }, + { "enable_random", (PyCFunction) enable_random, METH_VARARGS, NULL }, + { "enable_external", (PyCFunction) enable_external, METH_VARARGS, NULL }, + { "disable", (PyCFunction) disable, METH_VARARGS, NULL }, + { NULL } +}; + +PyMODINIT_FUNC initfiu_ll(void) +{ + PyObject *m; + + m = Py_InitModule("fiu_ll", fiu_functions); + + PyModule_AddIntConstant(m, "FIU_ONETIME", FIU_ONETIME); + + fiu_init(0); +} + diff --git a/bindings/python2/setup.py b/bindings/python2/setup.py new file mode 100644 index 0000000..dbdc256 --- /dev/null +++ b/bindings/python2/setup.py @@ -0,0 +1,17 @@ + +from distutils.core import setup, Extension + +fiu_ll = Extension("fiu_ll", + libraries = ['fiu'], + sources = ['fiu_ll.c']) + +setup( + name = 'fiu', + description = "libfiu bindings", + author = "Alberto Bertogli", + author_email = "albertito@blitiri.com.ar", + url = "http://blitiri.com.ar/p/libfiu", + py_modules = ['fiu'], + ext_modules = [fiu_ll] +) + diff --git a/doc/guide.rst b/doc/guide.rst new file mode 100644 index 0000000..eb2b736 --- /dev/null +++ b/doc/guide.rst @@ -0,0 +1,171 @@ + +libfiu - Fault injection in userspace +===================================== + +Introduction +------------ + +You, as a programmer, know many things can fail, and your software is often +expected to be able to handle those failures. But how do you test your failure +handling code, when it's not easy to make a failure appear in the first place? +One way to do it is to perform *fault injection*. + +According to Wikipedia, "fault injection is a technique for improving the +coverage of a test by introducing faults in order to test code paths, in +particular error handling code paths, that might otherwise rarely be followed. +It is often used with stress testing and is widely considered to be an +important part of developing robust software". + +libfiu is a library that you can use to add fault injection to your code. It +aims to be easy to use by means of a simple API, with minimal code impact and +little runtime overhead when enabled. + +By minimal code impact I mean that the modifications you have to do to your +code (and build system) in order to support libfiu should be as little +intrusive as possible. + + +Code overview +------------- + +Let's take a look to a small (fictitious) code sample to see what's the +general idea behind libfiu. + +Let's say you have this code that check if there's enough free space to fit a +given file:: + + size_t free_space() { + [code to find out how much free space there is] + return space; + } + + bool file_fits(FILE *fd) { + if (free_space() < file_size(fd)) { + return false; + } + return true; + } + +As you can see, with current disk sizes, it's very, very strange that a disk +runs out of free space. So the scenario where *free_space()* returns 0 is hard +to test. With libfiu, you can modify that code into:: + + size_t free_space() { + fiu_return_on("no_free_space", 0); + + [code to find out how much free space there is] + return space; + } + + bool file_fits(FILE *fd) { + if (free_space() < file_size(fd)) { + return false; + } + return true; + } + +There, you've just created a *point of failure* identified by the name +"no_free_space", and said that when that point of failure is enabled, the +function will return 0. + +In your testing code, you can now do this:: + + fiu_init(); + fiu_enable("no_free_space", 1, NULL, 0); + assert(file_fits("tmpfile") == false); + +The first line initializes the library, and the second *enables* the point of +failure. As the point of failure is enabled, *free_space()* will return 0, so +you can test how your code behaves under that condition, which was otherwise +hard to trigger. + +As you can see, libfiu's API has to "sides": a core API and a control API. The +core API is used inside the code wanting to perform failure injection on. The +control API is used inside the testing code, in order to control the injection +of failures. + +In the example above, *fiu_return_on()* is a part of the core API, and +*fiu_enable()* is a part of the control API. + + +Using libfiu in your project +---------------------------- + +To use libfiu in your project, there are three things to consider: the build +system, the fault injection code, and the testing code. + + +The build system +~~~~~~~~~~~~~~~~ + +The first thing to do is to enable your build system to use libfiu. Usually, +you do not want to make libfiu a runtime or build-time dependency, because +it's a testing library. + +To that end, you should copy *fiu-local.h* into your source tree, and then +create an option to do a *fault injection build* that #defines the constant +*ENABLE_FIU* (usually done by adding ``-DFIU_ENABLE=1`` to your compiler +flags) and links against libfiu (usually done by adding ``-lfiu`` to your +linker flags). + +That way, normal builds will not have a single trace of fault injection code, +but it will be easy to create a binary that does, for testing purposes. + + +The fault injection code +~~~~~~~~~~~~~~~~~~~~~~~~ + +Adding fault injection to your code means inserting points of failure in it, +using the core API. + +First, you should ``#include "fiu-local.h"`` in the files you want to add +points of failure to. That header allows you to avoid libfiu as a build-time +dependency, as mentioned in the last section. + +Then, you to insert points of failure, you sprinkle your code with calls like +``fiu_return_on("name", -1)``, ``fiu_exit_on("name")``, or more complex code +using ``fiu_fail("name")``. Consult the libfiu's manpage for the details on +the API. + +It is recommended that you use meaningful names for your points of failure, to +be able to easily identify their purpose. You can also name them +hierarchically (for example, using names like *"io/write"*, *"io/read"*, and +so on), to be able to enable entire groups of points of failure (like +*"io/\*"*,). To this end, any separator will do, the *'/'* is not special at +all. + + +The testing code +~~~~~~~~~~~~~~~~ + +Testing can be done in too many ways, so I won't get into specific details +here. As a general approach, usually the idea with fault injection is to write +tests similar in spirit to the one shown above: initialize the library, enable +one or more failures using the control API, and then check if the code behaves +as expected. + +Initially, all points of failure are disabled, which means your code should run +as usual, with a very small performance impact. + +The points of failure can be enabled using different strategies: + +Unconditional (*fiu_enable()*) + Enables the point of failure in an unconditional way, so it always fails. + +Random (*fiu_enable_random()*) + Enables the point of failure in a non-deterministic way, which will fail with + the given probability. + +External (*fiu_enable_external()*) + Enables the point of failure using an external function, which will be called + to determine whether the point of failure should fail or not. + +You can also use an asterisk *at the end* of a name to enable all the points +of failure that begin with the given name (excluding the asterisk, of course). + +Check libfiu's manpage for more details about the API. + +Using the Python bindings, you can also write and/or control your tests using +Python. + + diff --git a/libfiu/Makefile b/libfiu/Makefile new file mode 100644 index 0000000..17c95d3 --- /dev/null +++ b/libfiu/Makefile @@ -0,0 +1,77 @@ + +CFLAGS += -std=c99 -pedantic -Wall -O3 +ALL_CFLAGS = -D_XOPEN_SOURCE=500 -fPIC -DENABLE_FIU=1 $(CFLAGS) + +ifdef DEBUG +ALL_CFLAGS += -g +endif + +ifdef PROFILE +ALL_CFLAGS += -g -pg -fprofile-arcs -ftest-coverage +endif + + +# prefix for installing the binaries +PREFIX=/usr/local + + +OBJS = fiu.o + + +ifneq ($(V), 1) + NICE_CC = @echo " CC $@"; $(CC) +else + NICE_CC = $(CC) +endif + + +default: all + +all: libs libfiu.pc + +libfiu.pc: libfiu.skel.pc + @echo "generating libfiu.pc" + @cat libfiu.skel.pc | \ + sed 's@++PREFIX++@$(PREFIX)@g' \ + > libfiu.pc + +libs: libfiu.so libfiu.a + +libfiu.so: fiu.h $(OBJS) + $(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $(OBJS) -lpthread -o libfiu.so + +libfiu.a: fiu.h $(OBJS) + $(AR) cr libfiu.a $(OBJS) + + +install-lib: libs libfiu.pc + install -d $(PREFIX)/lib + install -m 0755 libfiu.so $(PREFIX)/lib + install -m 0755 libfiu.a $(PREFIX)/lib + install -d $(PREFIX)/include + install -m 0644 fiu.h $(PREFIX)/include + install -m 0644 fiu-control.h $(PREFIX)/include + install -m 0644 fiu-local.h $(PREFIX)/include + install -d $(PREFIX)/lib/pkgconfig + install -m 644 libfiu.pc $(PREFIX)/lib/pkgconfig + @echo + @echo "Please run ldconfig to update your library cache" + @echo + +install-man: + install -d $(PREFIX)/man/man3 + install -m 0644 libfiu.3 $(PREFIX)/man/man3/ + +install: install-lib install-man + + +.c.o: + $(NICE_CC) $(ALL_CFLAGS) -c $< -o $@ + +clean: + rm -f libfiu.pc $(OBJS) libfiu.so libfiu.a + rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out + +.PHONY: default all libs install-lib install-man install clean + + diff --git a/libfiu/fiu-control.h b/libfiu/fiu-control.h new file mode 100644 index 0000000..4a3baa5 --- /dev/null +++ b/libfiu/fiu-control.h @@ -0,0 +1,59 @@ + +/* + * Control API for libfiu + */ + +/* Flags */ +#define FIU_ONETIME 1 /* Only fail once */ + + +/* Enables the given point of failure. That makes it always fail. + * + * - name: point of failure name. + * - failnum: what will fiu_fail() return, must be != 0. + * - failinfo: what will fiu_failinfo() return. + * - flags: flags. + * - returns: 0 if success, < 0 otherwise. + * */ +int fiu_enable(const char *name, int failnum, void *failinfo, + unsigned int flags); + + +/* Enables the given point of failure, with the given probability. That makes + * it fail with the given probablity. + * + * - name: point of failure name. + * - failnum: what will fiu_fail() return, must be != 0. + * - failinfo: what will fiu_failinfo() return. + * - flags: flags. + * - probability: probability a fiu_fail() call will return failnum, + * between 0 (never fail) and 1 (always fail). As a special fast case, -1 + * can also be used to always fail. + * - returns: 0 if success, < 0 otherwise. */ +int fiu_enable_random(const char *name, int failnum, void *failinfo, + unsigned int flags, float probability); + + +/** Type of external callback functions. They must return 0 to indicate not to + * fail, != 0 to indicate otherwise. Can modify failnum, failinfo and flags, + * in order to alter the values of the point of failure. */ +typedef int external_cb_t(const char *name, int *failnum, void **failinfo, + unsigned int *flags); + +/* Enables the given point of failure, leaving the decision whether to fail or + * not to the given external function. + * + * - name: point of failure name. + * - failnum: what will fiu_fail() return, must be != 0. + * - failinfo: what will fiu_failinfo() return. + * - flags: flags. + * - external_cb: function to call to determine whether fail or not. */ +int fiu_enable_external(const char *name, int failnum, void *failinfo, + unsigned int flags, external_cb_t *external_cb); + +/* Disables the given point of failure. That makes it NOT fail. + * + * - name: point of failure name. + * - returns: 0 if success, < 0 otherwise. */ +int fiu_disable(const char *name); + diff --git a/libfiu/fiu-local.h b/libfiu/fiu-local.h new file mode 100644 index 0000000..b68327b --- /dev/null +++ b/libfiu/fiu-local.h @@ -0,0 +1,37 @@ + +/* libfiu - Fault Injection in Userspace + * + * This header, part of libfiu, is meant to be included in your project to + * avoid having libfiu as a mandatory build-time dependency. + * + * You can add it to your project, and #include it instead of fiu.h. + * The real fiu.h will be used only when FIU_ENABLE is defined. + * + * This header, as the rest of libfiu, is in the public domain. + * + * You can find more information about libfiu at + * http://blitiri.com.ar/p/libfiu. + */ + +#ifndef _FIU_LOCAL_H +#define _FIU_LOCAL_H + +/* Only define the stubs when fiu is disabled, otherwise use the real fiu.h + * header */ +#ifndef FIU_ENABLE + +#define fiu_init(flags) 0 +#define fiu_fail(name) 0 +#define fiu_failinfo() NULL +#define fiu_do_on(name, action) +#define fiu_exit_on(name) +#define fiu_return_on(name, retval) + +#else + +#include <fiu.h> + +#endif /* FIU_ENABLE */ + +#endif /* _FIU_LOCAL_H */ + diff --git a/libfiu/fiu.c b/libfiu/fiu.c new file mode 100644 index 0000000..b1f2330 --- /dev/null +++ b/libfiu/fiu.c @@ -0,0 +1,387 @@ + +#include <stdlib.h> /* malloc() and friends */ +#include <string.h> /* strcmp() and friends */ +#include <pthread.h> /* mutexes */ + +/* Enable us, so we get the real prototypes from the headers */ +#define FIU_ENABLE 1 + +#include "fiu.h" +#include "fiu-control.h" + + +/* Different methods to decide when a point of failure fails */ +enum pf_method { + PF_ALWAYS = 1, + PF_PROB, + PF_EXTERNAL, +}; + +/* Point of failure information */ +struct pf_info { + char *name; + unsigned int namelen; + int failnum; + void *failinfo; + unsigned int flags; + + /* How to decide when this point of failure fails, and the information + * needed to take the decision */ + enum pf_method method; + union { + /* To use when method == PF_PROB */ + float probability; + + /* To use when method == PF_EXTERNAL */ + external_cb_t *external_cb; + + } minfo; +}; + + +/* Array used to keep the information about the enabled points of failure. + * It's an array because we assume it's going to be short enough for the + * linear lookup not matter. + * In the future, if it turns out it's normal that it grows large enough, we + * may be interested in a more sophisticated structure like a hash table + * and/or a bloom filter. */ +static struct pf_info *enabled_fails = NULL; +static struct pf_info *enabled_fails_last = NULL; +static size_t enabled_fails_len = 0; +static size_t enabled_fails_nfree = 0; +static pthread_rwlock_t enabled_fails_lock; +#define ef_rlock() do { pthread_rwlock_rdlock(&enabled_fails_lock); } while (0) +#define ef_wlock() do { pthread_rwlock_wrlock(&enabled_fails_lock); } while (0) +#define ef_runlock() do { pthread_rwlock_unlock(&enabled_fails_lock); } while (0) +#define ef_wunlock() do { pthread_rwlock_unlock(&enabled_fails_lock); } while (0) + +/* Maximum number of free elements in enabled_fails (used to decide when to + * shrink). */ +#define EF_MAX_FREE 3 + +/* How much to grow enabled_fails by each time, it's recommended that this is + * less than EF_MAX_FREE. */ +#define EF_GROW 2 + + +/* Used to keep the last failinfo via TLS */ +static pthread_key_t last_failinfo_key; + +/* Used to avoid re-initialization, protected by enabled_fails_lock */ +static int initialized = 0; + + +/* + * Miscelaneous internal functions + */ + +/* Disables the given pf_info, assuming it's inside enabled_fails. Must be + * called with enabled_fails_lock acquired. */ +static void disable_pf(struct pf_info *pf) +{ + /* free the name we've allocated in setup_fail() via strdup() */ + free(pf->name); + pf->name = NULL; + pf->namelen = 0; + pf->failnum = 0; + pf->failinfo = NULL; + pf->flags = 0; +} + +/* Return the last position where s1 and s2 match. */ +static unsigned int strlast(const char *s1, const char *s2) +{ + unsigned int i = 0; + + while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) { + i++; + s1++; + s2++; + } + + return i; +} + +/* Checks if pf's name matches the one given. pf->name can be NULL. */ +static int name_matches(const struct pf_info *pf, const char *name, int exact) +{ + if (pf->name == NULL || name == NULL) + return 0; + + if (exact || pf->name[pf->namelen - 1] != '*') + return strcmp(pf->name, name) == 0; + + /* Inexact match */ + return strlast(pf->name, name) >= pf->namelen - 1; +} + +/* Shrink enabled_fails, used when it has too many free elements. Must be + * called with enabled_fails_lock acquired. */ +static int shrink_enabled_fails(void) +{ + int i; + size_t new_len; + struct pf_info *new, *pf; + + new_len = enabled_fails_len - enabled_fails_nfree + EF_GROW; + + new = malloc(new_len * sizeof(struct pf_info)); + if (new == NULL) + return -1; + + i = 0; + for (pf = enabled_fails; pf <= enabled_fails_last; pf++) { + if (pf->name == NULL) + continue; + + memcpy(new + i, pf, sizeof(struct pf_info)); + i++; + } + + memset(new + i, 0, (new_len - i) * sizeof(struct pf_info)); + + free(enabled_fails); + enabled_fails = new; + enabled_fails_len = new_len; + enabled_fails_last = new + new_len - 1; + enabled_fails_nfree = EF_GROW; + + return 0; +} + +/* + * Core API + */ + +/* Initializes the library. It should be safe to call this more than once at + * any time, to allow several independant libraries to use fiu at the same + * time without clashes. */ +int fiu_init(unsigned int flags) +{ + ef_wlock(); + if (!initialized) { + /* first time we get called */ + pthread_key_create(&last_failinfo_key, NULL); + + enabled_fails = NULL; + enabled_fails_last = NULL; + enabled_fails_len = 0; + enabled_fails_nfree = 0; + + initialized = 1; + } + + ef_wunlock(); + return 0; +} + +/* Returns the failure status of the given name. Must work well even before + * fiu_init() is called assuming no points of failure are enabled; although it + * can (and does) assume fiu_init() will be called before enabling any. */ +int fiu_fail(const char *name) +{ + struct pf_info *pf; + int failnum; + + ef_rlock(); + + if (enabled_fails == NULL) { + ef_runlock(); + return 0; + } + + for (pf = enabled_fails; pf <= enabled_fails_last; pf++) { + if (name_matches(pf, name, 0)) { + switch (pf->method) { + case PF_ALWAYS: + goto exit_fail; + break; + case PF_PROB: + if (pf->minfo.probability < drand48() ) + goto exit_fail; + break; + case PF_EXTERNAL: + if (pf->minfo.external_cb(pf->name, + &(pf->failnum), + &(pf->failinfo), + &(pf->flags))) + goto exit_fail; + break; + default: + break; + } + + break; + } + } + + ef_runlock(); + return 0; + +exit_fail: + pthread_setspecific(last_failinfo_key, + pf->failinfo); + failnum = pf->failnum; + + if (pf->flags & FIU_ONETIME) { + disable_pf(pf); + enabled_fails_nfree++; + } + + ef_runlock(); + return failnum; +} + +/* Returns the information associated with the last fail. */ +void *fiu_failinfo(void) +{ + return pthread_getspecific(last_failinfo_key); +} + + +/* + * Control API + */ + +/* Sets up the given pf. For internal use only. */ +static int setup_fail(struct pf_info *pf, const char *name, int failnum, + void *failinfo, unsigned int flags, enum pf_method method, + float probability, external_cb_t *external_cb) +{ + pf->name = strdup(name); + pf->namelen = strlen(name); + pf->failnum = failnum; + pf->failinfo = failinfo; + pf->flags = flags; + pf->method = method; + switch (method) { + case PF_ALWAYS: + break; + case PF_PROB: + pf->minfo.probability = probability; + break; + case PF_EXTERNAL: + pf->minfo.external_cb = external_cb; + break; + default: + return -1; + } + + if (pf->name == NULL) + return -1; + return 0; + +} + +/* Creates a new pf in the enabled_fails table. For internal use only. */ +static int insert_new_fail(const char *name, int failnum, void *failinfo, + unsigned int flags, enum pf_method method, float probability, + external_cb_t *external_cb) +{ + struct pf_info *pf; + int rv; + size_t prev_len; + + rv = -1; + + /* See if it's already there and update the data if so, or if we have + * a free spot where to put it */ + ef_wlock(); + if (enabled_fails != NULL && enabled_fails_nfree > 0) { + for (pf = enabled_fails; pf <= enabled_fails_last; pf++) { + if (pf->name == NULL || strcmp(pf->name, name) == 0) { + rv = setup_fail(pf, name, failnum, failinfo, + flags, method, probability, + external_cb); + if (rv == 0) + enabled_fails_nfree--; + + goto exit; + } + } + + /* There should be a free slot, but couldn't find one! This + * shouldn't happen */ + rv = -1; + goto exit; + } + + /* There are no free slots in enabled_fails, so we must grow it */ + enabled_fails = realloc(enabled_fails, + enabled_fails_len + EF_GROW * sizeof(struct pf_info)); + if (enabled_fails == NULL) { + enabled_fails_last = NULL; + enabled_fails_len = 0; + enabled_fails_nfree = 0; + rv = -1; + goto exit; + } + + prev_len = enabled_fails_len; + enabled_fails_len += EF_GROW; + enabled_fails_nfree = EF_GROW; + + memset(enabled_fails + prev_len, 0, + EF_GROW * sizeof(struct pf_info)); + + enabled_fails_last = enabled_fails + enabled_fails_len - 1; + + pf = enabled_fails + prev_len; + rv = setup_fail(pf, name, failnum, failinfo, flags, method, + probability, external_cb); + if (rv == 0) + enabled_fails_nfree--; + +exit: + ef_wunlock(); + return rv; +} + +/* Makes the given name fail. */ +int fiu_enable(const char *name, int failnum, void *failinfo, + unsigned int flags) +{ + return insert_new_fail(name, failnum, failinfo, flags, PF_ALWAYS, 0, + NULL); +} + +/* Makes the given name fail with the given probability. */ +int fiu_enable_random(const char *name, int failnum, void *failinfo, + unsigned int flags, float probability) +{ + return insert_new_fail(name, failnum, failinfo, flags, PF_PROB, + probability, NULL); +} + +/* Makes the given name fail when the external function returns != 0. */ +int fiu_enable_external(const char *name, int failnum, void *failinfo, + unsigned int flags, external_cb_t *external_cb) +{ + return insert_new_fail(name, failnum, failinfo, flags, PF_EXTERNAL, + 0, external_cb); +} + +/* Makes the given name NOT fail. */ +int fiu_disable(const char *name) +{ + struct pf_info *pf; + + /* just find the point of failure and mark it as free by setting its + * name to NULL */ + ef_wlock(); + for (pf = enabled_fails; pf <= enabled_fails_last; pf++) { + if (name_matches(pf, name, 1)) { + disable_pf(pf); + enabled_fails_nfree++; + if (enabled_fails_nfree > EF_MAX_FREE) + shrink_enabled_fails(); + ef_wunlock(); + return 0; + } + } + + ef_wunlock(); + return -1; +} + + diff --git a/libfiu/fiu.h b/libfiu/fiu.h new file mode 100644 index 0000000..e9fdf64 --- /dev/null +++ b/libfiu/fiu.h @@ -0,0 +1,71 @@ + +/* libfiu - Fault Injection in Userspace + * + * This header, part of libfiu, contains the API that your project should use. + * + * If you want to avoid having libfiu as a mandatory build-time dependency, + * you should include fiu-stubs.h in your project, and #include it instead of + * this. + */ + +#ifndef _FIU_H +#define _FIU_H + + +/* Controls whether the external code enables libfiu or not. */ +#ifdef FIU_ENABLE + +/* Initializes the library. + * + * - flags: unused. + * - returns: 0 if success, < 0 if error. */ +int fiu_init(unsigned int flags); + +/* Returns the failure status of the given point of failure. + * + * - name: point of failure name. + * - returns: the failure status (0 means it should not fail). */ +int fiu_fail(const char *name); + +/* Returns the information associated with the last failure. + * + * - returns: the information associated with the last fail, or NULL if there + * isn't one. */ +void *fiu_failinfo(void); + +/* Performs the given action when the given point of failure fails. Mostly + * used in the following macros. */ +#define fiu_do_on(name, action) \ + do { \ + if (fiu_fail(name)) { \ + action; \ + } \ + } while (0) + +/* Exits the program when the given point of failure fails. */ +#define fiu_exit_on(name) fiu_do_on(name, exit(EXIT_FAILURE)) + +/* Makes the function return the given retval when the given point of failure + * fails. */ +#define fiu_return_on(name, retval) fiu_do_on(name, return retval) + + +/* Undefine the private defines */ +#undef _likely +#undef _unlikely + +#else +/* fiu not enabled, this should match fiu-local.h but we don't include it + * because it includes us assuming we're installed system-wide */ + +#define fiu_init(flags) 0 +#define fiu_fail(name) 0 +#define fiu_failinfo() NULL +#define fiu_do_on(name, action) +#define fiu_exit_on(name) +#define fiu_return_on(name, retval) + +#endif /* FIU_ENABLE */ + +#endif /* _FIU_H */ + diff --git a/libfiu/libfiu.3 b/libfiu/libfiu.3 new file mode 100644 index 0000000..1f27c9b --- /dev/null +++ b/libfiu/libfiu.3 @@ -0,0 +1,170 @@ +.TH libfiu 3 "17/Feb/2009" +.SH NAME +libfiu - Fault injection in userspace +.SH SYNOPSYS +.nf +.B /* Core API */ +.B #include <fiu.h> +.sp +.BI "int fiu_init(unsigned int " flags ");" +.BI "int fiu_fail(const char *" name ");" +.BI "void *fiu_failinfo(void);" +.BI "[void] fiu_do_on(char *" name ", " action "); [macro]" +.BI "[void] fiu_exit_on(char *" name "); [macro]" +.BI "[void] fiu_return_on(char *" name ", " retval "); [macro]" +.sp +.B /* Control API */ +.B #include <fiu-control.h> +.sp +.BI "int fiu_enable(const char *" name ", int " failnum "," +.BI " void *" failinfo ", unsigned int " flags ");" +.BI "int fiu_enable_random(const char *" name ", int " failnum "," +.BI " void *" failinfo ", unsigned int " flags ", float " probability ");" +.BI "typedef int external_cb_t(const char *" name ", int *" failnum "," +.BI " void **" failinfo ", unsigned int *" flags ");" +.BI "int fiu_enable_external(const char *" name ", int " failnum "," +.BI " void *" failinfo ", unsigned int " flags "," +.BI " external_cb_t *" external_cb ");" +.BI "int fiu_disable(const char *" name ");" +.sp +.fi +.SH DESCRIPTION + +libfiu is a library for fault injection. It provides functions to mark "points +of failure" inside your code (the "core API"), and functions to enable/disable +the failure of those points (the "control API"). + +The core API is used inside the code wanting to perform failure injection on. +The control API is used inside the testing code, in order to control the +injection of failures. + +This page is an API reference and not a complete manual, and as such does not +go into detail about how to use the library. The library's manual can be found +in the distribution. + +.SS CORE API + +To use the core API, you should +.IR "#include <fiu.h>" . + +Because fault injection is usually a debugging/testing facility, unwanted at +runtime, some special considerations were taken to minimize the impact of the +core API. First of all, if +.I FIU_ENABLE +is not defined, then fiu.h will define empty stubs for all the core API, +effectively disabling fault injection completely, without any runtime +performance. + +Also, a special header +.I fiu-local.h +is shipped with libfiu. It is meant to be included in your project to avoid +having libfiu as a mandatory build-time dependency. You can add it to your +project, and #include it instead of +.IR fiu.h . +It will take care of including the real +.I fiu.h +only when +.I FIU_ENABLE +is defined. It is entirely optional, but recommended. See the library's manual +for more details. + + +.TP +.BI "int fiu_init(" flags ")" +Initializes the library. Ideally, you should only call this once, although it +can cope with multiple calls. The flags parameter is currently unused and must +be set to 0. Returns 0 on success, < 0 on error. + +.TP +.BI "fiu_fail(" name ")" +Returns the failure status of the given point of failure. 0 means it should +not fail. By default, all points of failure do not fail; they're enabled in +runtime using the control API. + +.TP +.BI "fiu_failinfo()" +Returns the information associated with the last failure, or NULL if there +isn't one. + +.TP +.BI "fiu_do_on(" name ", " action ") [macro]" +This is a macro that uses +.B fiu_fail() +to perform the given action when the given point of failure fails. The action +can be any valid C statement. + +.TP +.BI "fiu_exit_on(" name ") [macro]" +This is a macro that uses +.B fiu_fail() +to exit the process when the given point of failure fails. The process is exit +using exit(3), which is given the status EXIT_FAILURE. + +.TP +.BI "fiu_return_on(" name ", " retval ") [macro]" +This is a macro that uses +.B fiu_fail() +to make the current function return the given value (whose type obviously +depends on the return type of the function). + +.SS CONTROL API + +To use the control API, you should +.IR "#include <fiu-control.h>" . + +.TP +.BI "fiu_enable(" name ", " failnum ", " failinfo ", " flags ")" +Enables the given point of failure. +.I failnum +is what +.B fiu_fail() +will return, and must be != 0. +.I failinfo +is what +.B fiu_failinfo() +will return when called after the given point of failure has failed. +.I flags +can be either 0 or +.IR FIU_ONETIME , +which indicates that this point of failure should only fail once. Returns 0 if +success, < 0 otherwise. If the point of failure was already enabled, this +overwrites the previous values. + +Successive calls to +.B fiu_fail() +will return +.I failnum +until this point of failure is disabled. If +.I FIU_ONETIME +was passed in the flags, this point of failure is disabled immediately after +failing once. + +If the name ends with an asterisk, then it this will match all points of +failure that begin with the given name (excluding the asterisk, of course). + +.TP +.BI "fiu_enable_random(" name ", " failnum ", " failinfo ", " flags ", " probability ")" +Enables the given point of failure, with the given probability. The rest of the +parameters, as well as the return value, are the same as the ones in +.BR fiu_enable() . + +.TP +.BI "fiu_enable_external(" name ", " failnum ", " failinfo ", " flags ", " external_cb ")" +Enables the given point of failure, leaving the decision whether to fail or not +to the given external function, which should return 0 if it is not to fail, or +1 otherwise. The rest of the parameters, as well as the return value, are the +same as the ones in +.BR fiu_enable() . + +.TP +.BI "fiu_disable(" name ")" +Disables the given point of failure, undoing the actions of the +.B fiu_enable*() +functions. + +.SH BUGS + +If you want to report bugs, or have any questions or comments, just let me +know at albertito@blitiri.com.ar. For more information about the library, you +can go to http://blitiri.com.ar/p/libfiu. + diff --git a/libfiu/libfiu.skel.pc b/libfiu/libfiu.skel.pc new file mode 100644 index 0000000..838b4b6 --- /dev/null +++ b/libfiu/libfiu.skel.pc @@ -0,0 +1,12 @@ + +prefix=++PREFIX++ +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: libfiu +Description: Fault injection in userspace +URL: http://blitiri.com.ar/p/libfiu/ +Version: 0.10 +Libs: -L${libdir} -lfiu +Cflags: -I${includedir} +