* .gitignore: Add luajit_lib.h.
* Makefile.am: Build with LuaJIT if configured so.
(strace_SOURCES): Add defs_shared.h, ffi.h.
(luajit_lib.h): Auto-generate from luajit_lib.lua.
* NEWS: New entry.
* basic_filters.c: (syscall_classes): Make global and terminate with a
null entry.
(lookup_class): Use global syscall_classes.
* configure.ac: Add new --with-luajit configure option.
* defs.h (struct inject_opts): Move inject values to...
(struct inject_values): ...new struct.
(TCB_AD_HOC_INJECT, TCB_HOOK): New TCB flags.
(QUAL_HOOK_ENTRY, QUAL_HOOK_EXIT): New qual flags.
(RVAL_HOOKED): New return value flag.
(struct tcb): If built with LuaJIT support, include currpers field even
if SUPPORTED_PERSONALITIES is 1.
If built with LuaJIT support, include new ad_hoc_inject_vals field.
Move definitions that need to be fed to LuaJIT's FFI to...
* defs_shared.h: ...new file.
* ffi.h: New file.
* luajit.h: Likewise.
* luajit_lib.lua: Likewise.
* filter_qualify.c: (hook_entry_set, hook_exit_set): New sets (if built
with LuaJIT support).
(parse_inject_token, qualify_inject_common): Update for new layout of
struct inject_opts.
(alloc_hook_sets, set_hook_qual, set_hook_qual_all): New functions (if
built with LuaJIT support).
(qual_flags): Introduce QUALBIT macro.
If built with LuaJIT support, return QUAL_HOOK_ENTRY and QUAL_HOOK_EXIT
flags.
* number_set.c (number_unsetbit, remove_number_from_set,
extend_set_with_number, make_number_set_universal,
extend_set_array_with_number, make_number_set_array_universal): New
functions.
* strace.1 (LUA SCRIPTING): New section.
* strace.c (alloctcb): update the condition of presence of currpers
field.
(init): New -l option (if built with LuaJIT support).
(enum trace_event): New TE_SYSCALL_STOP_HOOK_EXIT entry.
(enum hook_state): New enum.
(trace_syscall): Introduce state argument.
(dispatch_event): Introduce hooked argument, support
TE_SYSCALL_STOP_HOOK_EXIT as first argument.
(main): run Lua script, if built with LuaJIT support and a script
was provided.
* syscall.c (errnoent_vec, nerrnoent_vec, signalent_vec,
nsignalent_vec, ioctlent_vec, nioctlent_vec, personality_wordsize,
personality_klongsize): New global variables.
(personality_names): New global variable (if SUPPORTED_PERSONALITIES >
1).
(update_personality): use personality_names for reporting personality
name.
(tcb_inject_opts): Introduce step argument, change return type to struct
inject_values, rename to tcb_inject_values.
If built with LuaJIT support and TCB_AD_HOC_INJECT flag is set, blend
the result with tcp's ad_hoc_inject_vals.
(tamper_with_syscall_entering): Don't copy inject_vec here and do
counter decrement logic here; pass true as a second argument to
tcb_inject_values.
(tamper_with_syscall_exiting): Pass false as a second argument to
tcb_inject_values.
(syscall_ad_hoc_inject): New function.
(syscall_entering_trace): perform ad hoc injection even if the syscall
is not traced.
(syscall_exiting_decode): don't return 0 ("bail out") if exiting hook is
set up for this syscall, or if an ad hoc injection was performed.
Call tamper_with_syscall_exiting on success.
(syscall_exiting_trace): Don't call tamper_with_syscall_exiting, check
if the syscall is not traced again.
(syscall_exiting_finish): Clear TCB_AD_HOC_INJECT bit.
* sysent.h: Modify to support inclusion with FFI_CDEF.
---
 .gitignore       |   1 +
 Makefile.am      |  17 ++
 NEWS             |   1 +
 basic_filters.c  |  60 +++--
 configure.ac     |  36 +++
 defs.h           |  72 +++---
 defs_shared.h    |  66 +++++
 ffi.h            |  19 ++
 filter_qualify.c |  84 +++++--
 luajit.h         | 311 +++++++++++++++++++++++
 luajit_lib.lua   | 378 ++++++++++++++++++++++++++++
 number_set.c     |  47 ++++
 number_set.h     |  15 ++
 strace.1.in      | 748 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 strace.c         |  95 +++++--
 syscall.c        | 234 ++++++++++++-----
 sysent.h         |  24 +-
 17 files changed, 2036 insertions(+), 172 deletions(-)
 create mode 100644 defs_shared.h
 create mode 100644 ffi.h
 create mode 100644 luajit.h
 create mode 100644 luajit_lib.lua

diff --git a/.gitignore b/.gitignore
index 6b226fba..ae78c075 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@
 /libmpers-m32.a
 /libmpers-mx32.a
 /libstrace.a
+/luajit_lib.h
 /m32_funcs.h
 /m32_printer_decls.h
 /m32_printer_defs.h
diff --git a/Makefile.am b/Makefile.am
index b3afc7c5..094aa903 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -107,6 +107,7 @@ strace_SOURCES =    \
        copy_file_range.c \
        count.c         \
        defs.h          \
+       defs_shared.h   \
        desc.c          \
        dirent.c        \
        dirent64.c      \
@@ -131,6 +132,7 @@ strace_SOURCES =    \
        fetch_struct_stat.c \
        fetch_struct_stat64.c \
        fetch_struct_statfs.c \
+       ffi.h           \
        file_handle.c   \
        file_ioctl.c    \
        filter_qualify.c \
@@ -330,6 +332,12 @@ strace_LDFLAGS += $(libunwind_LDFLAGS)
 strace_LDADD += $(libunwind_LIBS)
 endif
 
+if USE_LUAJIT
+strace_SOURCES += luajit.h
+strace_CPPFLAGS += $(LUAJIT_CFLAGS)
+strace_LDADD += $(LUAJIT_LIBS)
+endif
+
 @CODE_COVERAGE_RULES@
 CODE_COVERAGE_BRANCH_COVERAGE = 1
 CODE_COVERAGE_GENHTML_OPTIONS = $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) \
@@ -858,6 +866,7 @@ EXTRA_DIST =                                \
        linux/xtensa/set_scno.c         \
        linux/xtensa/syscallent.h       \
        linux/xtensa/userent.h          \
+       luajit_lib.lua                  \
        mpers.awk                       \
        mpers.sh                        \
        mpers_test.sh                   \
@@ -884,6 +893,9 @@ $(srcdir)/.version:
 strace_SOURCES_c = \
        $(filter %.c,$(strace_SOURCES)) $(filter %.c,$(libstrace_a_SOURCES))
 
+luajit_lib.h: luajit_lib.lua
+       sed 's/["\\]/\\\0/g;s/.*/"\0\\n"/' $< > $@
+
 sys_func.h: $(patsubst %,$(srcdir)/%,$(strace_SOURCES_c))
        for f in $^; do \
                sed -n 's/^SYS_FUNC(.*/extern &;/p' $$f; \
@@ -963,6 +975,11 @@ CLEANFILES    = $(ioctl_redefs_h) $(ioctlent_h) 
$(mpers_preproc_files) \
                native_printer_decls.h native_printer_defs.h printers.h sen.h 
sys_func.h
 DISTCLEANFILES = gnu/stubs-32.h gnu/stubs-x32.h
 
+if USE_LUAJIT
+BUILT_SOURCES += luajit_lib.h
+CLEANFILES += luajit_lib.h
+endif
+
 include scno.am
 
 $(strace_OBJECTS): scno.h
diff --git a/NEWS b/NEWS
index dbff0dec..3bcb0ad6 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ Noteworthy changes in release ?.?? (????-??-??)
 ===============================================
 
 * Improvements
+  * Added optional support for LuaJIT scripting (-l FILE option).
   * Enhanced decoding of optlen argument of getsockopt syscall.
   * Enhanced decoding of SO_LINGER option of getsockopt and setsockopt 
syscalls.
   * Enhanced decoding of SO_PEERCRED option of getsockopt syscall.
diff --git a/basic_filters.c b/basic_filters.c
index 7b7f0a54..0f31fd32 100644
--- a/basic_filters.c
+++ b/basic_filters.c
@@ -94,40 +94,38 @@ qualify_syscall_regex(const char *s, struct number_set *set)
        return found;
 }
 
+const struct syscall_class syscall_classes[] = {
+       { "desc",       TRACE_DESC      },
+       { "file",       TRACE_FILE      },
+       { "memory",     TRACE_MEMORY    },
+       { "process",    TRACE_PROCESS   },
+       { "signal",     TRACE_SIGNAL    },
+       { "ipc",        TRACE_IPC       },
+       { "network",    TRACE_NETWORK   },
+       { "%desc",      TRACE_DESC      },
+       { "%file",      TRACE_FILE      },
+       { "%memory",    TRACE_MEMORY    },
+       { "%process",   TRACE_PROCESS   },
+       { "%signal",    TRACE_SIGNAL    },
+       { "%ipc",       TRACE_IPC       },
+       { "%network",   TRACE_NETWORK   },
+       { "%stat",      TRACE_STAT      },
+       { "%lstat",     TRACE_LSTAT     },
+       { "%fstat",     TRACE_FSTAT     },
+       { "%%stat",     TRACE_STAT_LIKE },
+       { "%statfs",    TRACE_STATFS    },
+       { "%fstatfs",   TRACE_FSTATFS   },
+       { "%%statfs",   TRACE_STATFS_LIKE       },
+       {}
+};
+
 static unsigned int
 lookup_class(const char *s)
 {
-       static const struct {
-               const char *name;
-               unsigned int value;
-       } syscall_class[] = {
-               { "desc",       TRACE_DESC      },
-               { "file",       TRACE_FILE      },
-               { "memory",     TRACE_MEMORY    },
-               { "process",    TRACE_PROCESS   },
-               { "signal",     TRACE_SIGNAL    },
-               { "ipc",        TRACE_IPC       },
-               { "network",    TRACE_NETWORK   },
-               { "%desc",      TRACE_DESC      },
-               { "%file",      TRACE_FILE      },
-               { "%memory",    TRACE_MEMORY    },
-               { "%process",   TRACE_PROCESS   },
-               { "%signal",    TRACE_SIGNAL    },
-               { "%ipc",       TRACE_IPC       },
-               { "%network",   TRACE_NETWORK   },
-               { "%stat",      TRACE_STAT      },
-               { "%lstat",     TRACE_LSTAT     },
-               { "%fstat",     TRACE_FSTAT     },
-               { "%%stat",     TRACE_STAT_LIKE },
-               { "%statfs",    TRACE_STATFS    },
-               { "%fstatfs",   TRACE_FSTATFS   },
-               { "%%statfs",   TRACE_STATFS_LIKE       },
-       };
-
-       unsigned int i;
-       for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
-               if (strcmp(s, syscall_class[i].name) == 0) {
-                       return syscall_class[i].value;
+       const struct syscall_class *c;
+       for (c = syscall_classes; c->name; ++c) {
+               if (strcmp(s, c->name) == 0) {
+                       return c->value;
                }
        }
 
diff --git a/configure.ac b/configure.ac
index b4abdf8b..9ae4f949 100644
--- a/configure.ac
+++ b/configure.ac
@@ -716,6 +716,42 @@ AC_SUBST(dl_LIBS)
 
 AC_PATH_PROG([PERL], [perl])
 
+dnl LuaJIT scripting support
+use_luajit=no
+force_luajit=no
+luajit_lib=luajit
+LUAJIT_LIBS=
+LUAJIT_CFLAGS=
+AC_ARG_WITH([luajit],
+           [AS_HELP_STRING([--with-luajit],
+                           [build with LuaJIT scripting support, or use use 
Lua library with provided name])],
+                           [case "${withval}" in
+                            yes)   force_luajit=yes ;;
+                            check) ;;
+                            *)     force_luajit=yes; luajit_lib="${withval}" ;;
+                    esac],
+                    [:]
+                    )
+AS_IF([test "x$luajit_lib" != xno],
+      [PKG_CHECK_MODULES([LUAJIT],
+                        [$luajit_lib],
+                        [use_luajit=yes],
+                        [AS_IF([test "x$force_luajit" = xyes],
+                               [AC_MSG_ERROR([cannot find luajit library: 
$luajit_lib])]
+                               )]
+                        )]
+      )
+
+dnl enable LuaJIT
+AC_MSG_CHECKING([whether to enable Lua scripting])
+if test "x$use_luajit" = xyes; then
+       AC_DEFINE([USE_LUAJIT], 1, [Enable Lua scripting support])
+       AC_SUBST(LUAJIT_LIBS)
+       AC_SUBST(LUAJIT_CFLAGS)
+fi
+AM_CONDITIONAL([USE_LUAJIT], [test "x$use_luajit" = xyes])
+AC_MSG_RESULT([$use_luajit])
+
 dnl stack trace with libunwind
 libunwind_CPPFLAGS=
 libunwind_LDFLAGS=
diff --git a/defs.h b/defs.h
index 92cde26e..957c2d29 100644
--- a/defs.h
+++ b/defs.h
@@ -175,54 +175,21 @@ extern char *stpcpy(char *dst, const char *src);
 # define PERSONALITY2_INCLUDE_FUNCS "empty.h"
 #endif
 
-typedef struct ioctlent {
-       const char *symbol;
-       unsigned int code;
-} struct_ioctlent;
+struct inject_values {
+       uint16_t signo;
+       int rval;
+};
 
 struct inject_opts {
        uint16_t first;
        uint16_t step;
-       uint16_t signo;
-       int rval;
+       struct inject_values vals;
 };
 
 #define MAX_ERRNO_VALUE                        4095
-#define INJECT_OPTS_RVAL_DEFAULT       (-(MAX_ERRNO_VALUE + 1))
-
-/* Trace Control Block */
-struct tcb {
-       int flags;              /* See below for TCB_ values */
-       int pid;                /* If 0, this tcb is free */
-       int qual_flg;           /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW 
*/
-       unsigned long u_error;  /* Error code */
-       kernel_ulong_t scno;    /* System call number */
-       kernel_ulong_t u_arg[MAX_ARGS]; /* System call arguments */
-       kernel_long_t u_rval;   /* Return value */
-#if SUPPORTED_PERSONALITIES > 1
-       unsigned int currpers;  /* Personality at the time of scno update */
-#endif
-       int sys_func_rval;      /* Syscall entry parser's return value */
-       int curcol;             /* Output column for this process */
-       FILE *outf;             /* Output file for this process */
-       const char *auxstr;     /* Auxiliary info from syscall (see RVAL_STR) */
-       void *_priv_data;       /* Private data for syscall decoding functions 
*/
-       void (*_free_priv_data)(void *); /* Callback for freeing priv_data */
-       const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad 
scno */
-       const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" 
msg */
-       struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
-       struct timeval stime;   /* System time usage as of last process wait */
-       struct timeval dtime;   /* Delta for system time usage */
-       struct timeval etime;   /* Syscall entry time */
+#define INJECT_VALS_RVAL_DEFAULT       (-(MAX_ERRNO_VALUE + 1))
 
-#ifdef USE_LIBUNWIND
-       struct UPT_info *libunwind_ui;
-       struct mmap_cache_t *mmap_cache;
-       unsigned int mmap_cache_size;
-       unsigned int mmap_cache_generation;
-       struct queue_t *queue;
-#endif
-};
+#include "defs_shared.h"
 
 /* TCB flags */
 /* We have attached to this process, but did not see it stopping yet */
@@ -246,6 +213,8 @@ struct tcb {
 #define TCB_TAMPERED   0x40    /* A syscall has been tampered with */
 #define TCB_HIDE_LOG   0x80    /* We should hide everything (until execve) */
 #define TCB_SKIP_DETACH_ON_FIRST_EXEC  0x100   /* -b execve should skip detach 
on first execve */
+#define TCB_AD_HOC_INJECT      0x200   /* an ad hoc injection was performed by 
Lua script */
+#define TCB_HOOK       0x400   /* there is Lua hook for this syscall entry or 
exit */
 
 /* qualifier flags */
 #define QUAL_TRACE     0x001   /* this system call should be traced */
@@ -256,6 +225,8 @@ struct tcb {
 #define QUAL_SIGNAL    0x100   /* report events with this signal */
 #define QUAL_READ      0x200   /* dump data read from this file descriptor */
 #define QUAL_WRITE     0x400   /* dump data written to this file descriptor */
+#define QUAL_HOOK_ENTRY        0x800   /* return this syscall on entry from 
next_sc() */
+#define QUAL_HOOK_EXIT 0x1000  /* return this syscall on exit from next_sc() */
 
 #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
 
@@ -307,6 +278,7 @@ extern const struct xlat whence_codes[];
 #define RVAL_NONE      040     /* Print nothing */
 
 #define RVAL_DECODED   0100    /* syscall decoding finished */
+#define RVAL_HOOKED    0200    /* there is Lua hook for this syscall entry or 
exit */
 
 #define IOCTL_NUMBER_UNKNOWN 0
 #define IOCTL_NUMBER_HANDLED 1
@@ -352,6 +324,7 @@ typedef enum {
        CFLAG_ONLY_STATS,
        CFLAG_BOTH
 } cflag_t;
+extern const struct syscall_class syscall_classes[];
 extern cflag_t cflag;
 extern bool debug_flag;
 extern bool Tflag;
@@ -647,6 +620,12 @@ extern void print_ifindex(unsigned int);
 extern void qualify(const char *);
 extern unsigned int qual_flags(const unsigned int);
 
+#ifdef USE_LUAJIT
+extern void set_hook_qual(unsigned int scno, unsigned int pers, bool 
entry_hook,
+       bool exit_hook);
+extern void set_hook_qual_all(bool entry_hook, bool exit_hook);
+#endif
+
 #define DECL_IOCTL(name)                                               \
 extern int                                                             \
 name ## _ioctl(struct tcb *, unsigned int request, kernel_ulong_t arg) \
@@ -939,6 +918,19 @@ extern const char *const errnoent0[];
 extern const char *const signalent0[];
 extern const struct_ioctlent ioctlent0[];
 
+extern const char *const *errnoent_vec[SUPPORTED_PERSONALITIES];
+extern const char *const *signalent_vec[SUPPORTED_PERSONALITIES];
+extern const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES];
+extern const unsigned int nerrnoent_vec[SUPPORTED_PERSONALITIES];
+extern const unsigned int nsignalent_vec[SUPPORTED_PERSONALITIES];
+extern const unsigned int nioctlent_vec[SUPPORTED_PERSONALITIES];
+
+extern const int personality_wordsize[SUPPORTED_PERSONALITIES];
+extern const int personality_klongsize[SUPPORTED_PERSONALITIES];
+#if SUPPORTED_PERSONALITIES > 1
+extern const char *const personality_names[];
+#endif
+
 #if SUPPORTED_PERSONALITIES > 1
 extern const struct_sysent *sysent;
 extern const char *const *errnoent;
diff --git a/defs_shared.h b/defs_shared.h
new file mode 100644
index 00000000..1fb7a92d
--- /dev/null
+++ b/defs_shared.h
@@ -0,0 +1,66 @@
+/*
+ * Should only be included without FFI_CDEF from defs.h, so no include guards.
+ */
+
+#include "ffi.h"
+
+FFI_CONTENT(
+struct syscall_class {
+       const char *name;
+       unsigned int value;
+};
+)
+
+FFI_CONTENT(
+typedef struct ioctlent {
+       const char *symbol;
+       unsigned int code;
+} struct_ioctlent;
+)
+
+/* Trace Control Block */
+FFI_CONTENT(
+struct tcb {
+       int flags;              /* See below for TCB_ values */
+       int pid;                /* If 0, this tcb is free */
+       int qual_flg;           /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW 
*/
+       unsigned long u_error;  /* Error code */
+       kernel_ulong_t scno;    /* System call number */
+       kernel_ulong_t u_arg[MAX_ARGS]; /* System call arguments */
+       kernel_long_t u_rval;   /* Return value */
+)
+
+#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1
+FFI_CONTENT(
+       unsigned int currpers;  /* Personality at the time of scno update */
+)
+#endif
+
+#ifndef FFI_CDEF
+       int sys_func_rval;      /* Syscall entry parser's return value */
+       int curcol;             /* Output column for this process */
+       FILE *outf;             /* Output file for this process */
+       const char *auxstr;     /* Auxiliary info from syscall (see RVAL_STR) */
+       void *_priv_data;       /* Private data for syscall decoding functions 
*/
+       void (*_free_priv_data)(void *); /* Callback for freeing priv_data */
+       const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad 
scno */
+       const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" 
msg */
+# ifdef USE_LUAJIT
+       struct inject_values ad_hoc_inject_vals;
+# endif
+       struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
+       struct timeval stime;   /* System time usage as of last process wait */
+       struct timeval dtime;   /* Delta for system time usage */
+       struct timeval etime;   /* Syscall entry time */
+# ifdef USE_LIBUNWIND
+       struct UPT_info *libunwind_ui;
+       struct mmap_cache_t *mmap_cache;
+       unsigned int mmap_cache_size;
+       unsigned int mmap_cache_generation;
+       struct queue_t *queue;
+# endif
+#endif /* !FFI_CDEF */
+
+FFI_CONTENT(
+};
+)
diff --git a/ffi.h b/ffi.h
new file mode 100644
index 00000000..7b89e7a4
--- /dev/null
+++ b/ffi.h
@@ -0,0 +1,19 @@
+#ifndef STRACE_FFI_H
+#define STRACE_FFI_H
+
+#include "macros.h"
+
+#define FFI_CONCAT(a, b) a ## b
+#define FFI_CONCAT2(a, b) FFI_CONCAT(a, b)
+
+/*
+ * FFI_CONTENT expands to FFI_CONTENT_ (which strigifies its arguments) when
+ * FFI_CDEF is defined, and to FFI_CONTENT_FFI_CDEF (which simply expands to 
its
+ * arguments) when it is not.
+ */
+#define FFI_CONTENT FFI_CONCAT2(FFI_CONTENT_, FFI_CDEF)
+
+#define FFI_CONTENT_(...)         STRINGIFY(__VA_ARGS__)
+#define FFI_CONTENT_FFI_CDEF(...) __VA_ARGS__
+
+#endif /* !STRACE_FFI_H */
diff --git a/filter_qualify.c b/filter_qualify.c
index b59b19b9..3abe4a53 100644
--- a/filter_qualify.c
+++ b/filter_qualify.c
@@ -40,6 +40,10 @@ static struct number_set *inject_set;
 static struct number_set *raw_set;
 static struct number_set *trace_set;
 static struct number_set *verbose_set;
+#ifdef USE_LUAJIT
+static struct number_set *hook_entry_set;
+static struct number_set *hook_exit_set;
+#endif
 
 static int
 sigstr_to_uint(const char *s)
@@ -120,28 +124,28 @@ parse_inject_token(const char *const token, struct 
inject_opts *const fopts,
                        fopts->step = 0;
                }
        } else if ((val = STR_STRIP_PREFIX(token, "error=")) != token) {
-               if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
+               if (fopts->vals.rval != INJECT_VALS_RVAL_DEFAULT)
                        return false;
                intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
                if (intval < 0)
                        intval = find_errno_by_name(val);
                if (intval < 1)
                        return false;
-               fopts->rval = -intval;
+               fopts->vals.rval = -intval;
        } else if (!fault_tokens_only
                   && (val = STR_STRIP_PREFIX(token, "retval=")) != token) {
-               if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
+               if (fopts->vals.rval != INJECT_VALS_RVAL_DEFAULT)
                        return false;
                intval = string_to_uint(val);
                if (intval < 0)
                        return false;
-               fopts->rval = intval;
+               fopts->vals.rval = intval;
        } else if (!fault_tokens_only
                   && (val = STR_STRIP_PREFIX(token, "signal=")) != token) {
                intval = sigstr_to_uint(val);
                if (intval < 1 || intval > NSIG_BYTES * 8)
                        return false;
-               fopts->signo = intval;
+               fopts->vals.signo = intval;
        } else {
                return false;
        }
@@ -239,8 +243,10 @@ qualify_inject_common(const char *const str,
        struct inject_opts opts = {
                .first = 1,
                .step = 1,
-               .rval = INJECT_OPTS_RVAL_DEFAULT,
-               .signo = 0
+               .vals = {
+                       .rval = INJECT_VALS_RVAL_DEFAULT,
+                       .signo = 0,
+               }
        };
        char *buf = NULL;
        char *name = parse_inject_expression(str, &buf, &opts, 
fault_tokens_only);
@@ -249,10 +255,10 @@ qualify_inject_common(const char *const str,
        }
 
        /* If neither of retval, error, or signal is specified, then ... */
-       if (opts.rval == INJECT_OPTS_RVAL_DEFAULT && !opts.signo) {
+       if (opts.vals.rval == INJECT_VALS_RVAL_DEFAULT && !opts.vals.signo) {
                if (fault_tokens_only) {
                        /* in fault= syntax the default error code is ENOSYS. */
-                       opts.rval = -ENOSYS;
+                       opts.vals.rval = -ENOSYS;
                } else {
                        /* in inject= syntax this is not allowed. */
                        error_msg_and_die("invalid %s '%s'", description, str);
@@ -353,17 +359,57 @@ qualify(const char *str)
        opt->qualify(str);
 }
 
+#ifdef USE_LUAJIT
+static void
+alloc_hook_sets(void)
+{
+       if (!hook_entry_set)
+               hook_entry_set = alloc_number_set_array(
+                       SUPPORTED_PERSONALITIES);
+       if (!hook_exit_set)
+               hook_exit_set = alloc_number_set_array(
+                       SUPPORTED_PERSONALITIES);
+}
+
+void
+set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook,
+             bool exit_hook)
+{
+       alloc_hook_sets();
+       if (entry_hook)
+               extend_set_array_with_number(scno, hook_entry_set, pers);
+       if (exit_hook)
+               extend_set_array_with_number(scno, hook_exit_set, pers);
+}
+
+void
+set_hook_qual_all(bool entry_hook, bool exit_hook)
+{
+       alloc_hook_sets();
+       if (entry_hook)
+               make_number_set_array_universal(hook_entry_set,
+                       SUPPORTED_PERSONALITIES);
+       if (exit_hook)
+               make_number_set_array_universal(hook_exit_set,
+                       SUPPORTED_PERSONALITIES);
+}
+#endif
+
 unsigned int
 qual_flags(const unsigned int scno)
 {
-       return  (is_number_in_set_array(scno, trace_set, current_personality)
-                  ? QUAL_TRACE : 0)
-               | (is_number_in_set_array(scno, abbrev_set, current_personality)
-                  ? QUAL_ABBREV : 0)
-               | (is_number_in_set_array(scno, verbose_set, 
current_personality)
-                  ? QUAL_VERBOSE : 0)
-               | (is_number_in_set_array(scno, raw_set, current_personality)
-                  ? QUAL_RAW : 0)
-               | (is_number_in_set_array(scno, inject_set, current_personality)
-                  ? QUAL_INJECT : 0);
+#define QUALBIT(set, qualbit) \
+       (is_number_in_set_array(scno, set, current_personality) ? qualbit : 0)
+
+       return    QUALBIT(trace_set, QUAL_TRACE)
+               | QUALBIT(abbrev_set, QUAL_ABBREV)
+               | QUALBIT(verbose_set, QUAL_VERBOSE)
+               | QUALBIT(raw_set, QUAL_RAW)
+               | QUALBIT(inject_set, QUAL_INJECT)
+#ifdef USE_LUAJIT
+               | QUALBIT(hook_entry_set, QUAL_HOOK_ENTRY)
+               | QUALBIT(hook_exit_set, QUAL_HOOK_EXIT)
+#endif
+               ;
+#undef QUALBIT
 }
diff --git a/luajit.h b/luajit.h
new file mode 100644
index 00000000..fc17c8f9
--- /dev/null
+++ b/luajit.h
@@ -0,0 +1,311 @@
+/*
+ * Should only be included from strace.c, so no include guards.
+ */
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#define L script_L
+
+static struct tcb *
+func_next_sc(void)
+{
+       static bool first = true;
+       if (!first) {
+               if (!current_tcp)
+                       return NULL;
+               if (!dispatch_event(TE_SYSCALL_STOP_HOOK_EXIT, NULL, NULL,
+                   true))
+                       goto term;
+       }
+       first = false;
+
+       while (1) {
+               int status;
+               siginfo_t si;
+               enum trace_event ret = next_event(&status, &si);
+               if (!dispatch_event(ret, &status, &si, true))
+                       goto term;
+               if (ret == TE_SYSCALL_STOP && (current_tcp->flags & TCB_HOOK)) {
+                       current_tcp->flags &= ~TCB_HOOK;
+                       return current_tcp;
+               }
+       }
+
+term:
+       current_tcp = NULL;
+       return NULL;
+}
+
+static bool
+func_monitor(unsigned int scno, unsigned int pers, bool entry_hook,
+            bool exit_hook)
+{
+       if (pers >= SUPPORTED_PERSONALITIES)
+               return false;
+       set_hook_qual(scno, pers, entry_hook, exit_hook);
+       return true;
+}
+
+static void
+prepare_ad_hoc_inject(void)
+{
+       if (!(current_tcp->flags & TCB_AD_HOC_INJECT)) {
+               current_tcp->ad_hoc_inject_vals.signo = 0;
+               current_tcp->ad_hoc_inject_vals.rval = INJECT_VALS_RVAL_DEFAULT;
+               current_tcp->qual_flg |= QUAL_INJECT;
+               current_tcp->flags |= TCB_AD_HOC_INJECT;
+       }
+}
+
+static bool
+func_inject_signo(int signo)
+{
+       if (!current_tcp || exiting(current_tcp))
+               return false;
+       if (signo <= 0 || signo > SIGRTMAX)
+               return false;
+       prepare_ad_hoc_inject();
+       current_tcp->ad_hoc_inject_vals.signo = signo;
+       return true;
+}
+
+static bool
+func_inject_retval(int retval)
+{
+       if (!current_tcp || exiting(current_tcp))
+               return false;
+       if (retval < -MAX_ERRNO_VALUE)
+               return false;
+       prepare_ad_hoc_inject();
+       current_tcp->ad_hoc_inject_vals.rval = retval;
+       return true;
+}
+
+static int
+func_umove(kernel_ulong_t addr, size_t len, void *laddr)
+{
+       return current_tcp ? umoven(current_tcp, addr, len, laddr) : -1;
+}
+
+static int
+func_umove_str(kernel_ulong_t addr, size_t len, char *laddr)
+{
+       return current_tcp ? umovestr(current_tcp, addr, len, laddr) : -1;
+}
+
+static bool
+func_path_match(const char **set, size_t nset)
+{
+       if (!current_tcp)
+               return false;
+       struct path_set s = {set, nset};
+       return pathtrace_match_set(current_tcp, &s);
+}
+
+static const char *
+get_lua_msg(void)
+{
+       const char *msg = lua_tostring(L, -1);
+       return msg ? msg : "(error object can't be converted to string)";
+}
+
+static void
+assert_lua_impl(int ret, const char *expr, const char *file, int line)
+{
+       if (ret == 0)
+               return;
+       error_msg_and_die("assert_lua(%s) failed at %s:%d: %s", expr, file,
+               line, get_lua_msg());
+}
+
+#define assert_lua(expr) assert_lua_impl(expr, #expr, __FILE__, __LINE__)
+
+static void
+check_lua(int ret)
+{
+       if (ret == 0)
+               return;
+       error_msg_and_die("lua: %s", get_lua_msg());
+}
+
+#ifdef LUA_FFILIBNAME
+# define FFILIBNAME LUA_FFILIBNAME
+#else
+/* non-LuaJIT */
+# define FFILIBNAME "ffi"
+#endif
+
+#ifdef LUA_BITLIBNAME
+# define BITLIBNAME LUA_BITLIBNAME
+#else
+/* Lua <= 5.1 (non-LuaJIT) */
+# define BITLIBNAME "bit"
+#endif
+
+static void
+init_luajit(const char *scriptfile)
+{
+       if (L)
+               /* already initialized? */
+               error_msg_and_help("multiple -l arguments");
+
+       if (!(L = luaL_newstate()))
+               error_msg_and_die("luaL_newstate failed (out of memory?)");
+
+       luaL_openlibs(L);
+
+       lua_getglobal(L, "require"); /* L: require */
+       lua_pushstring(L, FFILIBNAME); /* L: require str */
+       assert_lua(lua_pcall(L, 1, 1, 0)); /* L: ffi */
+       lua_getfield(L, -1, "cdef"); /* L: ffi cdef */
+       luaL_Buffer b;
+       luaL_buffinit(L, &b); /* L: ffi cdef ? */
+       {
+               char buf[128];
+               snprintf(buf, sizeof(buf),
+                       "typedef int%d_t kernel_long_t;"
+                       "typedef uint%d_t kernel_ulong_t;",
+                       (int) sizeof(kernel_long_t) * 8,
+                       (int) sizeof(kernel_ulong_t) * 8);
+               luaL_addstring(&b, buf); /* L: ffi cdef ? */
+       }
+       const char *defs =
+#define FFI_CDEF
+#include "sysent.h"
+#include "defs_shared.h"
+#undef FFI_CDEF
+       ;
+       luaL_addstring(&b, defs); /* L: ffi cdef ? */
+       luaL_pushresult(&b); /* L: ffi cdef str */
+       assert_lua(lua_pcall(L, 1, 0, 0)); /* L: ffi */
+
+       lua_newtable(L); /* L: ffi strace */
+       lua_newtable(L); /* L: ffi strace C */
+
+       lua_getfield(L, 1, "cast"); /* L: ffi strace C cast */
+       lua_remove(L, 1); /* L: strace C cast */
+
+#define EXPOSE_FUNC(rettype, ptr, name, ...)                           \
+       do {                                                            \
+               rettype (*fptr_)(__VA_ARGS__) = ptr;                    \
+               lua_pushvalue(L, -1); /* L: strace C cast cast */       \
+               lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")");   \
+               /* L: strace C cast cast str */                         \
+               lua_pushlightuserdata(L, * (void **) (&fptr_));         \
+               /* L: strace C cast cast str ptr */                     \
+               assert_lua(lua_pcall(L, 2, 1, 0));                      \
+               /* L: strace C cast value */                            \
+               lua_setfield(L, -3, name); /* L: strace C cast */       \
+       } while (0)
+
+       EXPOSE_FUNC(bool, func_monitor, "monitor",
+               unsigned int, unsigned int, bool, bool);
+       EXPOSE_FUNC(void, set_hook_qual_all, "monitor_all",
+               bool, bool);
+       EXPOSE_FUNC(struct tcb *, func_next_sc, "next_sc",
+               void);
+       EXPOSE_FUNC(bool, func_inject_signo, "inject_signo",
+               int);
+       EXPOSE_FUNC(bool, func_inject_retval, "inject_retval",
+               int);
+       EXPOSE_FUNC(int, func_umove, "umove",
+               kernel_ulong_t, size_t, void *);
+       EXPOSE_FUNC(int, func_umove_str, "umove_str",
+               kernel_ulong_t, size_t, char *);
+       EXPOSE_FUNC(bool, func_path_match, "path_match",
+               const char **, size_t);
+
+#undef EXPOSE_FUNC
+
+#define EXPOSE(type, ptr, name)                                                
\
+       do {                                                            \
+               /* Get a compilation error/warning on type mismatch */  \
+               type tmp_ = ptr;                                        \
+               (void) tmp_;                                            \
+               lua_pushvalue(L, -1); /* L: strace C cast cast */       \
+               lua_pushstring(L, #type);                               \
+               /* L: strace C cast cast str */                         \
+               lua_pushlightuserdata(L, (void *) ptr);                 \
+               /* L: strace C cast cast str ptr */                     \
+               assert_lua(lua_pcall(L, 2, 1, 0));                      \
+               /* L: strace C cast value */                            \
+               lua_setfield(L, -3, name); /* L: strace C cast */       \
+       } while (0)
+
+       EXPOSE(const struct_sysent *const *, sysent_vec, "sysent_vec");
+       EXPOSE(const char *const **, errnoent_vec, "errnoent_vec");
+       EXPOSE(const char *const **, signalent_vec, "signalent_vec");
+       EXPOSE(const struct_ioctlent *const *, ioctlent_vec, "ioctlent_vec");
+
+       EXPOSE(const unsigned int *, nsyscall_vec, /*(!)*/ "nsysent_vec");
+       EXPOSE(const unsigned int *, nerrnoent_vec, "nerrnoent_vec");
+       EXPOSE(const unsigned int *, nsignalent_vec, "nsignalent_vec");
+       EXPOSE(const unsigned int *, nioctlent_vec, "nioctlent_vec");
+
+       EXPOSE(const struct syscall_class *, syscall_classes,
+               "syscall_classes");
+
+#if SUPPORTED_PERSONALITIES == 1
+       static const char *const personality_names[] = {#__WORDSIZE " bit"};
+#endif
+       EXPOSE(const char *const *, personality_names, "pers_name");
+       EXPOSE(const int *, personality_wordsize, "pers_wordsize");
+       EXPOSE(const int *, personality_klongsize, "pers_klongsize");
+
+#undef EXPOSE
+
+       lua_pop(L, 1); /* L: strace C */
+       lua_setfield(L, -2, "C"); /* L: strace */
+
+       lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: strace int */
+       lua_setfield(L, -2, "npersonalities"); /* L: strace */
+
+       lua_pushinteger(L, MAX_ARGS); /* L: strace int */
+       lua_setfield(L, -2, "max_args"); /* L: strace */
+
+       lua_pushinteger(L, PATH_MAX); /* L: strace int */
+       lua_setfield(L, -2, "path_max"); /* L: strace */
+
+       lua_setglobal(L, "strace"); /* L: - */
+
+       const char *code =
+#include "luajit_lib.h"
+       ;
+       assert_lua(luaL_loadstring(L, code)); /* L: chunk */
+
+       lua_newtable(L); /* L: chunk table */
+
+       lua_pushstring(L, FFILIBNAME); /* L: chunk table str */
+       lua_setfield(L, -2, "ffilibname"); /* L: chunk table */
+       lua_pushstring(L, BITLIBNAME); /* L: chunk table str */
+       lua_setfield(L, -2, "bitlibname"); /* L: chunk table */
+       lua_pushinteger(L, TCB_INSYSCALL); /* L: chunk table int */
+       lua_setfield(L, -2, "tcb_insyscall"); /* L: chunk table */
+       lua_pushinteger(L, QUAL_TRACE); /* L: chunk table int */
+       lua_setfield(L, -2, "qual_trace"); /* L: chunk table */
+       lua_pushinteger(L, QUAL_ABBREV); /* L: chunk table int */
+       lua_setfield(L, -2, "qual_abbrev"); /* L: chunk table */
+       lua_pushinteger(L, QUAL_VERBOSE); /* L: chunk table int */
+       lua_setfield(L, -2, "qual_verbose"); /* L: chunk table */
+       lua_pushinteger(L, QUAL_RAW); /* L: chunk table int */
+       lua_setfield(L, -2, "qual_raw"); /* L: chunk table */
+
+       assert_lua(lua_pcall(L, 1, 1, 0)); /* L: func */
+
+       check_lua(luaL_loadfile(L, scriptfile)); /* L: func chunk */
+}
+
+static void ATTRIBUTE_NORETURN
+run_luajit(void)
+{
+       /* L: func chunk */
+       check_lua(lua_pcall(L, 0, 0, 0)); /* L: func */
+       check_lua(lua_pcall(L, 0, 0, 0)); /* L: - */
+       terminate();
+}
+
+#undef FFILIBNAME
+#undef BITLIBNAME
+#undef assert_lua
+#undef L
diff --git a/luajit_lib.lua b/luajit_lib.lua
new file mode 100644
index 00000000..6280890e
--- /dev/null
+++ b/luajit_lib.lua
@@ -0,0 +1,378 @@
+-- This "chunk" of code is loaded and run before the script is.
+--
+-- To quote https://www.lua.org/manual/5.1/manual.html#2.4.1,
+-- "Lua handles a chunk as the body of an anonymous function with a variable
+--  number of arguments (see ยง2.5.9). As such, chunks can define local
+--  variables, receive arguments, and return values."
+--
+-- Thanks to Lua's support for closures, all the local variables defined here
+-- will not leak to another chunks (i.e., the script), but all the functions
+-- defined here can still access them.
+--
+-- strace calls this chunk with a single argument: a table with data that 
should
+-- not be exposed to the script, but is needed for some API functions defined
+-- here.
+--
+-- strace expects this chunk to return another function that will be run after
+-- the script returns.
+--
+-- Arguments passed to this chunk are accessible through the "..." vararg
+-- expression. The following line uses Lua's "adjust" assignment semantics to
+-- assign the first argument to a local variable "priv".
+local priv = ...
+
+local ffi = require(priv.ffilibname)
+ffi.cdef[[
+int strcmp(const char *, const char *);
+]]
+local bit = require(priv.bitlibname)
+
+local entry_cbs, exit_cbs, at_exit_cb = {}, {}, nil
+for p = 0, strace.npersonalities - 1 do
+       entry_cbs[p] = {}
+       exit_cbs[p] = {}
+end
+
+local nullptr = ffi.NULL
+pcall(function()
+       nullptr = ffi.C.NULL
+end)
+
+local function chain(f, g)
+       if not f then
+               return g
+       end
+       return function(...)
+               f(...)
+               g(...)
+       end
+end
+
+local function register_hook(scno, pers, on_entry, on_exit, cb)
+       assert(not not strace.C.monitor(scno, pers, on_entry, on_exit))
+       scno, pers = tonumber(scno), tonumber(pers)
+       if on_entry then
+               entry_cbs[pers][scno] = chain(entry_cbs[pers][scno], cb)
+       end
+       if on_exit then
+               exit_cbs[pers][scno] = chain(exit_cbs[pers][scno], cb)
+       end
+end
+
+function strace.entering(tcp)
+       return bit.band(tcp.flags, priv.tcb_insyscall) == 0
+end
+
+function strace.exiting(tcp)
+       return bit.band(tcp.flags, priv.tcb_insyscall) ~= 0
+end
+
+local function alter_trace_opt(flagbit, tcp, ...)
+       if strace.exiting(tcp) then
+               return false
+       end
+       -- i.e., if ... is empty, or the first element of ... is true
+       if select('#', ...) == 0 or select(1, ...) then
+               tcp.qual_flg = bit.bor(tcp.qual_flg, flagbit)
+       else
+               tcp.qual_flg = bit.band(tcp.qual_flg, bit.bnot(flagbit))
+       end
+       return true
+end
+for funcname, flagbit in pairs{
+       trace   = priv.qual_trace,
+       abbrev  = priv.qual_abbrev,
+       verbose = priv.qual_verbose,
+       raw     = priv.qual_raw,
+} do
+       strace[funcname] = function(tcp, ...)
+               return alter_trace_opt(flagbit, tcp, ...)
+       end
+end
+
+function strace.ptr_to_kulong(ptr)
+       return ffi.cast('kernel_ulong_t', ffi.cast('unsigned long', ptr))
+end
+
+function strace.at_exit(f)
+       at_exit_cb = chain(at_exit_cb, f)
+end
+
+function strace.get_sc_name(scno, pers)
+       if scno < 0 or scno >= strace.C.nsysent_vec[pers] then
+               return nil
+       end
+       local s = strace.C.sysent_vec[pers][scno].sys_name
+       return s ~= nullptr and ffi.string(s) or nil
+end
+
+function strace.get_sig_name(signo, pers)
+       if signo < 0 or signo >= strace.C.nsignalent_vec[pers] then
+               return nil
+       end
+       local s = strace.C.signalent_vec[pers][signo]
+       return s ~= nullptr and ffi.string(s) or nil
+end
+
+function strace.get_err_name(err, pers)
+       if err < 0 or err > strace.C.nerrnoent_vec[pers] then
+               return nil
+       end
+       local s = strace.C.errnoent_vec[pers][err]
+       return s ~= nullptr and ffi.string(s) or nil
+end
+
+function strace.get_ioctl_name(code, pers)
+       -- we could have provided a definition for stdlib's bsearch() and used
+       -- it, but LuaJIT's FFI manual says generated callbacks are a limited
+       -- resource and also slow. So implement binary search ourselves.
+       local lb, rb = ffi.cast('unsigned int', 0), strace.C.nioctlent_vec[pers]
+       if rb == 0 then
+               return nil
+       end
+       local arr = strace.C.ioctlent_vec[pers]
+       while rb - lb > 1 do
+               local mid = lb + (rb - lb) / 2
+               if arr[mid].code <= code then
+                       lb = mid
+               else
+                       rb = mid
+               end
+       end
+       return arr[lb].code == code and ffi.string(arr[lb].symbol) or nil
+end
+
+function strace.get_ioctl_code(name, pers)
+       local cstr = ffi.cast('const char *', name)
+       local arr = strace.C.ioctlent_vec[pers]
+       for i = 0, strace.C.nioctlent_vec[pers] do
+               if ffi.C.strcmp(arr[i].symbol, cstr) == 0 then
+                       return arr[i].code
+               end
+       end
+       return nil
+end
+
+function strace.get_scno(scname, pers)
+       local cstr = ffi.cast('const char *', scname)
+       for i = 0, tonumber(strace.C.nsysent_vec[pers]) - 1 do
+               local s = strace.C.sysent_vec[pers][i].sys_name
+               if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+                       return i
+               end
+       end
+       return nil
+end
+
+function strace.get_signo(signame, pers)
+       local cstr = ffi.cast('const char *', signame)
+       for i = 0, tonumber(strace.C.nsignalent_vec[pers]) - 1 do
+               local s = strace.C.signalent_vec[pers][i]
+               if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+                       return i
+               end
+       end
+       return nil
+end
+
+function strace.get_errno(errname, pers)
+       local cstr = ffi.cast('const char *', errname)
+       for i = 0, tonumber(strace.C.nerrnoent_vec[pers]) - 1 do
+               local s = strace.C.errnoent_vec[pers][i]
+               if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+                       return i
+               end
+       end
+       return nil
+end
+
+function strace.inject_signal(tcp, sig)
+       local signo = tonumber(sig)
+       if not signo then
+               signo = strace.get_signo(sig, tcp.currpers)
+               if not signo then
+                       return false
+               end
+       end
+       return not not strace.C.inject_signo(signo)
+end
+
+function strace.inject_error(tcp, err)
+       local errno = tonumber(err)
+       if not errno then
+               errno = strace.get_errno(err, tcp.currpers)
+               if not errno then
+                       return false
+               end
+       end
+       return errno > 0 and not not strace.C.inject_retval(-errno)
+end
+
+local ptr_size = ffi.sizeof('void *')
+
+function strace.read_obj(addr, ct, ...)
+       local obj = ffi.new(ct, ...)
+       local n = ffi.sizeof(obj)
+       -- work around FFI pointer semantics
+       if n == ptr_size then
+               -- It may be a pointer, and it is cheap to create another copy.
+               -- FFI templating ('$ [1]') fails for variable-length arrays, so
+               -- fall back to normal umove in case of error.
+               local is_ok, ret = pcall(function()
+                       local t = ffi.typeof(obj)
+                       local arr = ffi.typeof('$ [1]', t)()
+                       return strace.C.umove(addr, n, arr) == 0 and t(arr[0])
+                               or nil
+               end)
+               if is_ok then
+                       return ret
+               end
+       end
+       return strace.C.umove(addr, n, obj) == 0 and obj or nil
+end
+
+function strace.read_str(addr, maxsz, bufsz)
+       -- convert it to Lua number to prevent underflows
+       maxsz = tonumber(maxsz or 4 * 1024 * 1024)
+       bufsz = bufsz or 1024
+       local t = {}
+       local buf = ffi.new('char [?]', bufsz)
+       while true do
+               local r = strace.C.umove_str(addr, bufsz, buf)
+               if r < 0 then
+                       return nil, 'readerr'
+               elseif r == 0 then
+                       maxsz = maxsz - bufsz
+                       if maxsz < 0 then
+                               return nil, 'toolong'
+                       end
+                       t[#t + 1] = ffi.string(buf, bufsz)
+                       addr = addr + bufsz
+               else
+                       local s = ffi.string(buf)
+                       if #s > maxsz then
+                               return nil, 'toolong'
+                       end
+                       return table.concat(t) .. s
+               end
+       end
+end
+
+function strace.read_path(addr)
+       return strace.read_str(addr, strace.path_max, strace.path_max)
+end
+
+local function parse_when(when)
+       if type(when) == 'table' then
+               return unpack(when)
+       elseif when == 'entering' then
+               return true, false
+       elseif when == 'exiting' then
+               return false, true
+       elseif when == 'both' then
+               return true, true
+       else
+               error('unknown "when" value')
+       end
+end
+
+local function reduce_or(f, args, ...)
+       local ret = false
+       for _, arg in ipairs(args) do
+               if f(arg, ...) then
+                       ret = true
+               end
+       end
+       return ret
+end
+
+function strace.hook(scname, when, cb)
+       local on_entry, on_exit = parse_when(when)
+       if type(scname) == 'table' then
+               return reduce_or(strace.hook, scname, {on_entry, on_exit}, cb)
+       end
+       local found = false
+       for p = 0, strace.npersonalities - 1 do
+               local scno = strace.get_scno(scname, p)
+               if scno then
+                       register_hook(scno, p, on_entry, on_exit, cb)
+                       found = true
+               end
+       end
+       return found
+end
+
+function strace.hook_class(clsname, when, cb)
+       local on_entry, on_exit = parse_when(when)
+       if type(clsname) == 'table' then
+               return reduce_or(strace.hook_class, clsname,
+                       {on_entry, on_exit}, cb)
+       end
+       local cstr = ffi.cast('const char *', clsname)
+       local flag = nil
+       local ptr = strace.C.syscall_classes
+       while ptr.name ~= nullptr do
+               if ffi.C.strcmp(ptr.name, cstr) == 0 then
+                       flag = ptr.value
+                       break
+               end
+               ptr = ptr + 1
+       end
+       if not flag then
+               return false
+       end
+       for p = 0, strace.npersonalities - 1 do
+               for i = 0, tonumber(strace.C.nsysent_vec[p]) - 1 do
+                       if bit.band(strace.C.sysent_vec[p][i].sys_flags, flag)
+                          ~= 0
+                       then
+                               register_hook(i, p, on_entry, on_exit, cb)
+                       end
+               end
+       end
+       return true
+end
+
+function strace.hook_scno(scno, pers, when, cb)
+       local on_entry, on_exit = parse_when(when)
+       if type(scno) == 'table' then
+               return reduce_or(strace.hook_scno, scno, pers,
+                       {on_entry, on_exit}, cb)
+       end
+       register_hook(scno, pers, on_entry, on_exit, cb)
+end
+
+function strace.path_match(set)
+       if type(set) ~= 'table' then
+               set = {set}
+       end
+       local nset = #set
+       return not not strace.C.path_match(
+               ffi.new('const char *[?]', nset, set), nset)
+end
+
+function print(...)
+       local sep = ''
+       for i = 1, select('#', ...) do
+               io.stderr:write(sep .. tostring(select(i, ...)))
+               sep = '\t'
+       end
+       io.stderr:write('\n')
+end
+
+return function()
+       while true do
+               local tcp = strace.C.next_sc()
+               if tcp == nullptr then
+                       break
+               end
+               local cb = (strace.entering(tcp) and entry_cbs or exit_cbs)
+                       [tonumber(tcp.currpers)][tonumber(tcp.scno)]
+               if cb then
+                       cb(tcp)
+               end
+       end
+       if at_exit_cb then
+               at_exit_cb()
+       end
+end
diff --git a/number_set.c b/number_set.c
index b8aa28c7..a2e049a1 100644
--- a/number_set.c
+++ b/number_set.c
@@ -50,6 +50,12 @@ number_setbit(const unsigned int i, number_slot_t *const vec)
        vec[i / BITS_PER_SLOT] |= (number_slot_t) 1 << (i % BITS_PER_SLOT);
 }
 
+static void
+number_unsetbit(const unsigned int i, number_slot_t *const vec)
+{
+       vec[i / BITS_PER_SLOT] &= ~((number_slot_t) 1 << (i % BITS_PER_SLOT));
+}
+
 static bool
 number_isset(const unsigned int i, const number_slot_t *const vec)
 {
@@ -97,6 +103,29 @@ add_number_to_set(const unsigned int number, struct 
number_set *const set)
 }
 
 void
+remove_number_from_set(const unsigned int number, struct number_set *const set)
+{
+       if (number / BITS_PER_SLOT < set->nslots)
+               number_unsetbit(number, set->vec);
+}
+
+void
+extend_set_with_number(const unsigned int number, struct number_set *const set)
+{
+       if (set->not)
+               remove_number_from_set(number, set);
+       else
+               add_number_to_set(number, set);
+}
+
+void
+make_number_set_universal(struct number_set *const set)
+{
+       free(set->vec);
+       *set = (struct number_set) { .not = true };
+}
+
+void
 add_number_to_set_array(const unsigned int number, struct number_set *const 
set,
                        const unsigned int idx)
 {
@@ -104,6 +133,14 @@ add_number_to_set_array(const unsigned int number, struct 
number_set *const set,
 }
 
 void
+extend_set_array_with_number(const unsigned int number,
+                            struct number_set *const set,
+                            const unsigned int idx)
+{
+       extend_set_with_number(number, &set[idx]);
+}
+
+void
 clear_number_set_array(struct number_set *const set, const unsigned int nmemb)
 {
        unsigned int i;
@@ -125,6 +162,16 @@ invert_number_set_array(struct number_set *const set, 
const unsigned int nmemb)
                set[i].not = !set[i].not;
 }
 
+void
+make_number_set_array_universal(struct number_set *set,
+                               const unsigned int nmemb)
+{
+       unsigned int i;
+
+       for (i = 0; i < nmemb; ++i)
+               make_number_set_universal(&set[i]);
+}
+
 struct number_set *
 alloc_number_set_array(const unsigned int nmemb)
 {
diff --git a/number_set.h b/number_set.h
index 967a1c88..81a43c2b 100644
--- a/number_set.h
+++ b/number_set.h
@@ -43,14 +43,29 @@ extern void
 add_number_to_set(unsigned int number, struct number_set *);
 
 extern void
+remove_number_from_set(unsigned int number, struct number_set *);
+
+extern void
+extend_set_with_number(unsigned int number, struct number_set *);
+
+extern void
+make_number_set_universal(struct number_set *);
+
+extern void
 add_number_to_set_array(unsigned int number, struct number_set *, unsigned int 
idx);
 
 extern void
+extend_set_array_with_number(unsigned int number, struct number_set *, 
unsigned int idx);
+
+extern void
 clear_number_set_array(struct number_set *, unsigned int nmemb);
 
 extern void
 invert_number_set_array(struct number_set *, unsigned int nmemb);
 
+extern void
+make_number_set_array_universal(struct number_set *, unsigned int nmemb);
+
 extern struct number_set *
 alloc_number_set_array(unsigned int nmemb);
 
diff --git a/strace.1.in b/strace.1.in
index dc830086..f315e11d 100644
--- a/strace.1.in
+++ b/strace.1.in
@@ -53,6 +53,10 @@
 .  el \
 .    BR "\\$1"
 ..
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
 .TH STRACE 1 "@MANPAGE_DATE@" "strace @VERSION@"
 .SH NAME
 strace \- trace system calls and signals
@@ -795,6 +799,750 @@ Print the help summary.
 .B \-V
 Print the version number of
 .BR strace .
+.SH LUA SCRIPTING
+If built with Lua support,
+.B strace
+can execute Lua scripts.
+A script file is passed to the
+.B -l
+option.
+.PP
+.B strace
+provides the built-in module
+.B strace
+to the Lua execution environment, which contains various functions and
+constants.
+.PP
+Before any tracing takes place, the script is run.
+At this stage, it can do one of the following two things:
+.RS
+.IP \(bu 3
+Implement its own tracing loop by selecting syscalls it wants to be notified
+about with
+.BR strace.C.monitor / strace.C.monitor_all
+and calling
+.B strace.C.next_sc
+in a loop until it returns a null pointer (or return earlier; in this case, the
+installed hooks for the remaining syscalls are run).
+.IP \(bu
+Install syscall and at-exit hooks with
+.BR strace.hook ", " strace.hook_class ", " strace.hook_scno " and " 
strace.at_exit .
+.RE
+.PP
+Then,
+.B strace
+enters its own tracing loop, and all the installed hooks are run.
+.SS Personalities
+A personality is an execution domain with its own sets of syscalls, signals and
+errors.
+A
+.I personality number
+is strace-specific 0-based index in the architecture-dependent list of
+personalities.
+.SS FFI
+.URL http://luajit.org/ext_ffi.html "FFI library"
+is an extension provided by LuaJIT;
+.URL https://github.com/jmckaskill/luaffi luaffi
+and
+.URL https://github.com/facebook/luaffifb luaffifb
+standalone implementations are also supported, but only for Lua 5.2.
+.PP
+One may want to look at
+.URL https://github.com/m-schmoock/lcpp lcpp ,
+a pure-Lua implementation of C preprocessor, which integrates with LuaJIT's FFI
+library and supports inclusion of header files and exposing all the definitions
+and macros to the FFI.
+As for now, lcpp does not support system include directories, and treats all
+includes as local.
+.URL https://github.com/umegaya/ffiex ffiex ,
+a library based on it, on the other hand, does support them, and thus can be
+used to preprocess system headers.
+.PP
+.B strace
+provides the following FFI definitions:
+.CW
+typedef /* implementation-defined signed integer type   */ kernel_long_t;
+typedef /* implementation-defined unsigned integer type */ kernel_ulong_t;
+
+typedef struct sysent {
+    unsigned nargs;          /* Number of arguments */
+    int sys_flags;           /* Flags.  Currently, only meaningful in the
+                              * context of struct syscall_class::value field:
+                              * a syscall belongs to a class iff
+                              * (class.value & syscall.sys_flags) != 0. */
+    const char *sys_name;    /* Name */
+} struct_sysent;
+
+struct syscall_class {
+    const char *name;        /* Name */
+    unsigned int value;      /* Flag bit, see the comment on struct
+                              * sysent::sys_flags field. */
+};
+
+typedef struct ioctlent {
+    const char *symbol;
+    unsigned int code;
+} struct_ioctlent;
+
+/* Trace control block */
+struct tcb {
+    int pid;                              /* Tracee's PID */
+    unsigned long u_error;                /* Error code */
+    kernel_ulong_t scno;                  /* System call number */
+    kernel_ulong_t u_arg[/* MAX_ARGS */]; /* System call arguments */
+    kernel_ulong_t u_rval;                /* Return value */
+    unsigned int currpers;                /* Current personality */
+};
+.CE
+.SS strace module: C submodule: functions
+Note: be careful with boxed boolean values and use
+.B not not
+.I boxed_bool
+when in doubt.
+In particular, an
+.B assert
+on a boxed boolean will never raise an error.
+.PP
+Note: not checking the result of
+.B strace.C.next_sc
+against a null pointer is likely the reason you get a segmentation fault.
+Note that unless you select some syscalls with
+.BR strace.C.monitor / strace.C.monitor_all ,
+it will always return a null pointer.
+.TP
+\fIstatus\fR = \fBstrace.C.monitor\fR(\fIscno\fR, \fIpers\fR, \fIon_entry\fR, 
\fIon_exit\fR)
+C type:
+.B bool (*)(unsigned int, unsigned int, bool, bool)
+.IP
+Marks the syscall with number
+.I scno
+on personality
+.I pers
+as to be returned from
+.BR strace.next_sc .
+If
+.I on_entry
+is
+.BR true ,
+it is marked as to be returned on syscall entry, and if
+.I on_exit
+is
+.BR true ,
+it is marked as to be returned on syscall exit.
+.IP
+Note that this "marking" is a one-way process, and specifying
+.B false
+as any of the flags does not undo any previous calls to
+.BR strace.C.monitor / strace.C.monitor_all .
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fBstrace.C.monitor_all\fR(\fIon_entry\fR, \fIon_exit\fR)
+C type:
+.B void (*)(bool, bool)
+.IP
+Marks all syscalls on all personalities as to be returned from
+.BR strace.C.next_sc :
+on syscall entry if
+.I on_entry
+is
+.BR true ,
+and on syscall exit if
+.I on_exit
+is
+.BR true .
+See the note for
+.BR strace.C.monitor .
+.TP
+\fItcp\fR = \fBstrace.C.next_sc\fR()
+C type:
+.B struct tcb * (*)(void)
+.IP
+If this is not the first call to this function, performs tracing and tampering
+of the previously returned syscall.
+.IP
+Waits for the next monitored syscall to happen, and returns a pointer to its
+trace control block.
+.IP
+If
+.B strace
+needs to be terminated (e.g. last tracee has been terminated, or
+.B strace
+has been interrupted), returns a null pointer.
+Once it returned a null pointer, all subsequent calls to it will also return 
it.
+.TP
+\fIstatus\fR = \fBstrace.C.inject_signo\fR(\fIsigno\fR)
+C type:
+.B bool (*)(int)
+.IP
+Delivers a signal with number
+.I signo
+to the current tracee.
+.IP
+Note that this must be done on syscall entry.
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.inject_retval\fR(\fIval\fR)
+C type:
+.B bool (*)(int)
+.IP
+Injects a return value to the current syscall invocation.
+.IP
+Note that this must be done on syscall entry.
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.umove\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR)
+C type:
+.B int (*)(kernel_ulong_t, size_t, void *)
+.IP
+Copies ("moves")
+.I len
+bytes of data from the current tracee process at address
+.I addr
+to a local address
+.IR laddr .
+.IP
+Returns 0 on success and \-1 on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.umove_str\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR)
+C type:
+.B int (*)(kernel_ulong_t, size_t, char *)
+.IP
+Like
+.BR strace.C.umove ,
+but makes the additional effort of looking for a terminating zero byte.
+Returns a negative value on failure, a positive value if a NUL was seen, and 0
+if
+.I len
+bytes were read but no NUL seen.
+.IP
+Note: there is no guarantee it won't overwrite some bytes in \fIladdr\fR after
+terminating NUL (but, of course, it never writes past
+.IR laddr[len-1] ).
+.TP
+\fIstatus\fR = \fBstrace.C.path_match\fR(\fIset\fR, \fInset\fR)
+C type:
+.B bool (*)(const char **, size_t)
+.IP
+Returns
+.B true
+if the current syscall accesses one of the paths from a given set of paths, and
+.B false
+otherwise.
+.IP
+Note: for string path arguments, the path is compared against the set; for file
+descriptor arguments, the absolute path to the file behind the file descriptor
+is compared against the set.
+.SS strace module: C submodule: constants
+.TP
+.B strace.C.sysent_vec
+Array of syscall tables for each of the supported personalities.
+\fBstrace.C.sysent_vec\fR[\fIpers\fR][\fIscno\fR] is a
+.B struct_sysent
+for syscall number
+.I scno
+on personality
+.IR pers .
+May contain null entries (which have a NULL
+.B sys_name
+field).
+.TP
+.B strace.C.errnoent_vec
+Array of error name tables for each of the supported personalities.
+\fBstrace.C.errnoent_vec\fR[\fIpers\fR][\fIerrno\fR] is either a null pointer 
or
+a C string with the name of error
+.I errno
+on personality
+.IR pers .
+.TP
+.B strace.C.signalent_vec
+Array of signal name tables for each of the supported personalities.
+\fBstrace.C.signalent_vec\fR[\fIpers\fR][\fIsigno\fR] is either a null pointer
+or a C string with the name of signal
+.I signo
+on personality
+.IR pers .
+.TP
+.B strace.C.ioctlent_vec
+Arrays of sorted known ioctl symbols, sorted by code, for each of the supported
+personalities.
+\fBstrace.C.ioctlent_vec\fR[\fIpers\fR][\fIi\fR] is the
+.IR i \-th,
+ranked by code,
+.B struct_ioctlent
+for personality
+.IR pers .
+.TP
+.B strace.C.nsysent_vec
+.TQ
+.B strace.C.nerrnoent_vec
+.TQ
+.B strace.C.nsignalent_vec
+.TQ
+.B strace.C.nioctlent_vec
+These are
+.BR strace.npersonalities \-sized
+arrays containing sizes of subarrays of
+.BR strace.C.sysent_vec ,
+.BR strace.C.errnoent_vec ,
+.BR strace.C.signalent_vec " and"
+.BR strace.C.ioctlent_vec ,
+respectively.
+.TP
+.B strace.C.syscall_classes
+Array of
+.BR "struct syscall_class" ,
+with a terminating null entry (which has a NULL
+.B name
+field).
+.TP
+.B strace.C.pers_name
+A
+.BR strace.npersonalities \-sized
+array of C strings with names for each personality.
+.TP
+.B strace.C.pers_wordsize
+.TQ
+.B strace.C.pers_klongsize
+These are
+.BR strace.npersonalities \-sized
+arrays with word and
+.I __kernel_long_t
+sizes for each personality.
+.SS strace module: functions
+Glossary:
+.RS
+.IP \(bu 3
+an
+.I integer
+means either an integer Lua number or a cdata integer type;
+.IP \(bu
+a
+.I boolean
+means either a Lua boolean or a cdata
+.BR bool ;
+.IP \(bu
+a
+.I string
+means either a Lua string or a cdata C string;
+.IP \(bu
+the
+.I current tracing control pointer
+is a pointer to the current trace control block.
+This pointer is obtained from
+.B strace.C.next_sc
+calls and provided to hook callback functions.
+These pointers are not persistent across .B strace.C.next_sc and/or hook calls.
+.RE
+.PP
+General conventions:
+.RS
+.IP \(bu 3
+a
+.I tcp
+argument is the
+.IR "current tracing control pointer" ;
+.IP \(bu
+a
+.I pers
+argument is an
+.I integer
+specifying personality number;
+.IP \(bu
+an
+.I addr
+argument is a cdata
+.IR kernel_ulong_t .
+.RE
+.TP
+\fIstatus\fR = \fBstrace.entering\fR(\fItcp\fR)
+Returns
+.B true
+if this is a syscall entry, and
+.B false
+otherwise.
+.TP
+\fIstatus\fR = \fBstrace.exiting\fR(\fItcp\fR)
+Returns
+.B true
+if this is a syscall exit, and
+.B false
+otherwise.
+.TP
+\fBstrace.trace\fR(\fItcp\fR[, \fIflag\fR])
+.TQ
+\fBstrace.abbrev\fR(\fItcp\fR[, \fIflag\fR])
+.TQ
+\fBstrace.verbose\fR(\fItcp\fR[, \fIflag\fR])
+.TQ
+\fBstrace.raw\fR(\fItcp\fR[, \fIflag\fR])
+These functions alter corresponding trace options.
+.I flag
+is a
+.IR boolean ,
+defaults to
+.BR true .
+.TP
+\fIaddr\fR = \fBstrace.ptr_to_kulong\fR(\fIptr\fR)
+Converts a cdata pointer to a
+.B kernel_ulong_t
+variable.
+.TP
+\fBstrace.at_exit\fR(\fIfunc\fR)
+Registers a function
+.I func
+to be run when
+.B strace
+is going to terminate its execution.
+.IP
+If multiple exit callback functions are registered, they are run in the order
+they were registered.
+.TP
+\fIname\fR = \fBstrace.get_sc_name\fR(\fIscno\fR, \fIpers\fR)
+Returns syscall name as Lua string by its number for personality
+.IR pers ,
+or
+.B nil
+if
+.I scno
+is invalid.
+.IP
+.I scno
+is an
+.IR integer .
+.TP
+\fIname\fR = \fBstrace.get_sig_name\fR(\fIsigno\fR, \fIpers\fR)
+Returns signal name (.e.g \fB"SIGSEGV"\fR) as Lua string by its number
+.I signo
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I signo
+is invalid.
+.TP
+\fIname\fR = \fBstrace.get_err_name\fR(\fIerrno\fR, \fIpers\fR)
+Returns error name (e.g. \fB"ENOENT"\fR) as Lua string by its error number
+.I errno
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I errno
+is invalid.
+.IP
+.I errno
+is an
+.IR integer .
+.TP
+\fIname\fR = \fBstrace.get_ioctl_name\fR(\fIreqcode\fR, \fIpers\fR)
+Returns ioctl symbol name (e.g. \fB"TIOCGWINSZ"\fR) as Lua string by its 
request
+code
+.I reqcode
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I reqcode
+is invalid.
+.IP
+.I reqcode
+is an
+.IR integer .
+.TP
+\fIscno\fR = \fBstrace.get_scno\fR(\fIscname\fR, \fIpers\fR)
+Returns syscall number by its name for personality
+.IR pers ,
+or
+.B nil
+if no such syscall was found.
+.IP
+.I scname
+is a
+.IR string .
+.TP
+\fIsigno\fR = \fBstrace.get_signo\fR(\fIsigname\fR, \fIpers\fR)
+Returns signal number by its name (e.g. \fB"SIGSEGV"\fR) for personality
+.IR pers ,
+or
+.B nil
+if no such signal was found.
+.IP
+.I signame
+is
+.IR string .
+.TP
+\fIerrno\fR = \fBstrace.get_errno\fR(\fIerrname\fR, \fIpers\fR)
+Returns error number by its name (e.g. \fB"ENOENT"\fR) for personality
+.IR pers ,
+or
+.B nil
+if no such error was found.
+.IP
+.I errname
+is a
+.IR string .
+.TP
+\fIcode\fR = \fBstrace.get_ioctl_code\fR(\fIname\fR, \fIpers\fR)
+Returns ioctl request code by its symbol name (e.g. \fB"TIOCGWINSZ"\fR) for
+personality
+.IR pers ,
+or
+.B nil
+if no such ioctl request was found.
+.IP
+.I name
+is a
+.IR string .
+.TP
+\fIstatus\fR = \fBstrace.inject_signal\fR(\fItcp\fR, \fIsig\fR)
+Delivers a signal to the tracee.
+.I sig
+is either signal number (an
+.IR integer )
+or name (a
+.IR string ).
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.IP
+Note that this must be done on syscall entry.
+.TP
+\fIstatus\fR = \fBstrace.inject_error\fR(\fItcp\fR, \fIerr\fR)
+Injects an error into a current syscall invocation.
+.I err
+is either error number (an
+.IR integer )
+or error name (a
+.IR string ).
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.IP
+Note that this must be done on syscall entry.
+.TP
+\fIobj\fR = \fBstrace.read_obj\fR(\fIaddr\fR, \fIct\fR[, \fInelem\fR])
+Reads an object of type
+.I ct
+from the current tracee process at address
+.IR addr .
+.I ct
+is either a
+.I cdecl
+(a Lua string), a
+.I cdata
+serving as a template type, or a
+.I ctype
+(special kind of
+.I cdata
+returned by
+.BR ffi.typeof ).
+.IP
+VLA/VLS types require the
+.I nelem
+argument (an
+.IR integer ).
+.IP
+Returns an object on success and
+.B nil
+on failure.
+.IP
+Note that type sizes and structure paddings may differ from FFI's ones if
+tracee's personality differs from
+.BR strace 's
+one.
+.TP
+\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_str\fR(\fIaddr\fR[, \fImaxsz\fR[, 
\fIbufsz\fR]])
+Reads a C string from the current tracee process at address
+.I addr
+using an intermediate buffer of size
+.I bufsz
+and stopping at
+.I maxsz
+bytes.
+.IP
+.I maxsz
+and
+.I bufsz
+are
+.IR integers .
+.I maxsz
+defaults to 4 Mb,
+.I bufsz
+to 1 Kb.
+.IP
+Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and
+\fBnil, "toolong"\fR if the
+.I maxsz
+limit was exceeded.
+.TP
+\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_path\fR(\fIaddr\fR)
+Reads a path C string from the current tracee process at address
+.IR addr .
+.IP
+Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and
+\fBnil, "toolong"\fR if the
+.B PATH_MAX
+limit was exceeded.
+.TP
+\fIstatus\fR = \fBstrace.hook\fR(\fIscname\fR, \fIwhen\fR, \fIcb\fR)
+.TQ
+\fIstatus\fR = \fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR)
+.TQ
+\fIstatus\fR = \fBstrace.hook_scno\fR(\fIscno\fR, \fIpers\fR, \fIwhen\fR, 
\fIcb\fR)
+These functions register a function
+.I cb
+to be run when a syscall with the given name (or with a name from a given set
+thereof), belonging to a class with the given name (or with a name from a given
+set thereof), or with the given number (or with a number from a given set
+thereof) on personality
+.IR pers ,
+happens.
+.IP
+.I when
+is either a two-element array (that is, a table with keys 1 and 2) with boolean
+values, or a Lua string.
+.I cb
+is registered to be run:
+.RS
+.IP \(bu 3
+on syscall entry if
+.I when
+is either
+.B {true, false}
+or \fB"entering"\fR;
+.IP \(bu
+on syscall exit if
+.I when
+is either
+.B {false, true}
+or \fB"exiting"\fR;
+.IP \(bu
+both on syscall entry and exit if
+.I when
+is either
+.B {true, true}
+or \fB"both"\fR;
+.IP \(bu
+at no time if
+.I when
+is
+.B "{false, false}"
+(in this case, a call is effectively ignored).
+.RE
+.IP
+A pointer to the trace control block is passed as the only argument to
+.IR cb .
+.IP
+If multiple callback functions are registered for the same event (syscall entry
+or exit), they are run in the order they were registered.
+.IP
+.IR scname " and " clsname
+are either
+.I strings
+or tables thereof.
+.I scno
+is either an
+.I integer
+or a table thereof.
+.IP
+Return
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.path_match\fR(\fIset\fR)
+Returns
+.B true
+if the current syscall accesses a given path, or one of the paths from the 
given
+set of paths; and
+.B false
+otherwise (see the note for
+.BR strace.C.path_match ).
+.IP
+.I set
+is either a
+.I string
+or a table thereof.
+.SS strace module: constants
+.TP
+.B strace.npersonalities
+Number of supported personalities (an integer Lua number).
+.TP
+.B strace.max_args
+Size of
+.B struct tcb::u_arg
+array (an integer Lua number).
+.TP
+.B strace.path_max
+Value of
+.B PATH_MAX
+constant (an integer Lua number).
+.SS Examples
+The following script counts the number of processes (including threads) spawned
+by the tracee.
+Note that you would probably want to launch
+.B strace
+with
+.B -f
+option, so that children also be traced.
+.CW
+n = 0
+assert(strace.hook({'clone', 'fork', 'vfork'}, 'exiting', function(tcp)
+    if tcp.u_rval ~= -1 then
+        n = n + 1
+    end
+end))
+strace.at_exit(function() print('Processes spawned:', n) end)
+.CE
+The following script uses the ffiex library (see the discussion in the
+.B FFI
+section) and injects the
+.B EPERM
+error to each
+.B fcntl
+(and
+.BR fcntl64 )
+syscall invocation with
+.B F_SETPIPE_SZ
+command.
+.CW
+ffiex = require 'ffiex'
+ffiex.cdef[[
+#define _GNU_SOURCE
+#include <fcntl.h>
+]]
+f_setpipe_sz = assert(tonumber(ffiex.defs.F_SETPIPE_SZ))
+assert(strace.hook({'fcntl', 'fcntl64'}, 'entering', function(tcp)
+    if tcp.u_arg[1] == f_setpipe_sz then
+        assert(strace.inject_error(tcp, 'EPERM'))
+    end
+end))
+.CE
 .SH DIAGNOSTICS
 When
 .I command
diff --git a/strace.c b/strace.c
index 6ed86a6f..470b2768 100644
--- a/strace.c
+++ b/strace.c
@@ -45,6 +45,9 @@
 # include <sys/prctl.h>
 #endif
 #include <asm/unistd.h>
+#ifdef USE_LUAJIT
+# include <lua.h>
+#endif
 
 #include "number_set.h"
 #include "scno.h"
@@ -170,6 +173,11 @@ static volatile sig_atomic_t interrupted;
 static volatile int interrupted;
 #endif
 
+#ifdef USE_LUAJIT
+static lua_State *script_L = NULL;
+static void init_luajit(const char *scriptfile);
+#endif
+
 #ifndef HAVE_STRERROR
 
 #if !HAVE_DECL_SYS_ERRLIST
@@ -228,6 +236,11 @@ Output format:\n\
   -k             obtain stack trace between each syscall (experimental)\n\
 "
 #endif
+#ifdef USE_LUAJIT
+"\
+  -l file        run a Lua script from FILE\n\
+"
+#endif
 "\
   -o file        send trace output to FILE instead of stderr\n\
   -q             suppress messages about attaching, detaching, etc.\n\
@@ -713,7 +726,7 @@ alloctcb(int pid)
                if (!tcp->pid) {
                        memset(tcp, 0, sizeof(*tcp));
                        tcp->pid = pid;
-#if SUPPORTED_PERSONALITIES > 1
+#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1
                        tcp->currpers = current_personality;
 #endif
 
@@ -1589,6 +1602,9 @@ init(int argc, char *argv[])
 #ifdef USE_LIBUNWIND
                "k"
 #endif
+#ifdef USE_LUAJIT
+               "l:"
+#endif
                "D"
                "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
                switch (c) {
@@ -1699,6 +1715,11 @@ init(int argc, char *argv[])
                        stack_trace_enabled = true;
                        break;
 #endif
+#ifdef USE_LUAJIT
+               case 'l':
+                       init_luajit(optarg);
+                       break;
+#endif
                case 'E':
                        if (putenv(optarg) < 0)
                                perror_msg_and_die("putenv");
@@ -2216,6 +2237,9 @@ enum trace_event {
         */
        TE_SYSCALL_STOP,
 
+       /* Syscall entry or exit, after hook. */
+       TE_SYSCALL_STOP_HOOK_EXIT,
+
        /*
         * Tracee received signal with number WSTOPSIG(*pstatus); signal info
         * is written to *si.  Restart the tracee (with that signal number
@@ -2406,24 +2430,47 @@ next_event(int *pstatus, siginfo_t *si)
        }
 }
 
+enum hook_state {
+       HOOK_ENTER,
+       HOOK_EXIT,
+       HOOK_IGNORE,
+};
+
 static int
-trace_syscall(struct tcb *tcp, unsigned int *sig)
+trace_syscall(struct tcb *tcp, unsigned int *sig, enum hook_state state)
 {
        if (entering(tcp)) {
-               int res = syscall_entering_decode(tcp);
-               switch (res) {
-               case 0:
-                       return 0;
-               case 1:
+               int res;
+               switch (state) {
+               case HOOK_ENTER:
+               case HOOK_IGNORE:
+                       res = syscall_entering_decode(tcp);
+                       if (res == 0)
+                               return 0;
+                       if (res == 1)
+                               if (state == HOOK_ENTER &&
+                                   (tcp->qual_flg & QUAL_HOOK_ENTRY))
+                                       return RVAL_HOOKED;
+                       /* Fall through */
+               case HOOK_EXIT:
                        res = syscall_entering_trace(tcp, sig);
                }
                syscall_entering_finish(tcp, res);
                return res;
        } else {
-               struct timeval tv = {};
-               int res = syscall_exiting_decode(tcp, &tv);
-               if (res != 0) {
-                       res = syscall_exiting_trace(tcp, tv, res);
+               static struct timeval tv;
+               int res = 1;
+               switch (state) {
+               case HOOK_ENTER:
+               case HOOK_IGNORE:
+                       res = syscall_exiting_decode(tcp, &tv);
+                       if (res == 1 && state == HOOK_ENTER &&
+                           (tcp->qual_flg & QUAL_HOOK_EXIT))
+                               return RVAL_HOOKED;
+                       /* Fall through */
+               case HOOK_EXIT:
+                       if (res != 0)
+                               res = syscall_exiting_trace(tcp, tv, res);
                }
                syscall_exiting_finish(tcp);
                return res;
@@ -2432,10 +2479,11 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)
 
 /* Returns true iff the main trace loop has to continue. */
 static bool
-dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
+dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si, bool hooked)
 {
        unsigned int restart_op = PTRACE_SYSCALL;
        unsigned int restart_sig = 0;
+       int res;
 
        switch (ret) {
        case TE_BREAK:
@@ -2448,7 +2496,17 @@ dispatch_event(enum trace_event ret, int *pstatus, 
siginfo_t *si)
                break;
 
        case TE_SYSCALL_STOP:
-               if (trace_syscall(current_tcp, &restart_sig) < 0) {
+       case TE_SYSCALL_STOP_HOOK_EXIT:
+               res = trace_syscall(current_tcp, &restart_sig,
+                       hooked ? (ret == TE_SYSCALL_STOP ? HOOK_ENTER : 
HOOK_EXIT) :
+                       HOOK_IGNORE);
+
+               if (res == RVAL_HOOKED) {
+                       current_tcp->flags |= TCB_HOOK;
+                       return true;
+               }
+
+               if (res < 0) {
                        /*
                         * ptrace() failed in trace_syscall().
                         * Likely a result of process disappearing mid-flight.
@@ -2584,6 +2642,10 @@ terminate(void)
        exit(exit_code);
 }
 
+#ifdef USE_LUAJIT
+# include "luajit.h"
+#endif
+
 int
 main(int argc, char *argv[])
 {
@@ -2591,9 +2653,14 @@ main(int argc, char *argv[])
 
        exit_code = !nprocs;
 
+#ifdef USE_LUAJIT
+       if (script_L)
+               run_luajit();
+#endif
+
        int status;
        siginfo_t si;
-       while (dispatch_event(next_event(&status, &si), &status, &si))
+       while (dispatch_event(next_event(&status, &si), &status, &si, false))
                ;
        terminate();
 }
diff --git a/syscall.c b/syscall.c
index d5aeabf0..d4dc06ed 100644
--- a/syscall.c
+++ b/syscall.c
@@ -160,6 +160,16 @@ enum {
 #endif
 };
 
+const char *const *errnoent_vec[SUPPORTED_PERSONALITIES] = {
+       errnoent0,
+#if SUPPORTED_PERSONALITIES > 1
+       errnoent1,
+# if SUPPORTED_PERSONALITIES > 2
+       errnoent2,
+# endif
+#endif
+};
+
 enum {
        nerrnos0 = ARRAY_SIZE(errnoent0)
 #if SUPPORTED_PERSONALITIES > 1
@@ -170,6 +180,16 @@ enum {
 #endif
 };
 
+const unsigned int nerrnoent_vec[] = {
+       nerrnos0,
+#if SUPPORTED_PERSONALITIES > 1
+       nerrnos1,
+# if SUPPORTED_PERSONALITIES > 2
+       nerrnos2,
+# endif
+#endif
+};
+
 enum {
        nsignals0 = ARRAY_SIZE(signalent0)
 #if SUPPORTED_PERSONALITIES > 1
@@ -180,6 +200,26 @@ enum {
 #endif
 };
 
+const char *const *signalent_vec[SUPPORTED_PERSONALITIES] = {
+       signalent0,
+#if SUPPORTED_PERSONALITIES > 1
+       signalent1,
+# if SUPPORTED_PERSONALITIES > 2
+       signalent2,
+# endif
+#endif
+};
+
+const unsigned int nsignalent_vec[] = {
+       nsignals0,
+#if SUPPORTED_PERSONALITIES > 1
+       nsignals1,
+# if SUPPORTED_PERSONALITIES > 2
+       nsignals2,
+# endif
+#endif
+};
+
 enum {
        nioctlents0 = ARRAY_SIZE(ioctlent0)
 #if SUPPORTED_PERSONALITIES > 1
@@ -190,6 +230,26 @@ enum {
 #endif
 };
 
+const unsigned int nioctlent_vec[] = {
+       nioctlents0,
+#if SUPPORTED_PERSONALITIES > 1
+       nioctlents1,
+# if SUPPORTED_PERSONALITIES > 2
+       nioctlents2,
+# endif
+#endif
+};
+
+const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES] = {
+       ioctlent0,
+#if SUPPORTED_PERSONALITIES > 1
+       ioctlent1,
+# if SUPPORTED_PERSONALITIES > 2
+       ioctlent2,
+# endif
+#endif
+};
+
 #if SUPPORTED_PERSONALITIES > 1
 const struct_sysent *sysent = sysent0;
 const char *const *errnoent = errnoent0;
@@ -222,29 +282,55 @@ const struct_sysent *const 
sysent_vec[SUPPORTED_PERSONALITIES] = {
 #endif
 };
 
+const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
+       PERSONALITY0_WORDSIZE,
+#if SUPPORTED_PERSONALITIES > 1
+       PERSONALITY1_WORDSIZE,
+#endif
+#if SUPPORTED_PERSONALITIES > 2
+       PERSONALITY2_WORDSIZE,
+#endif
+};
+
+const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
+       PERSONALITY0_KLONGSIZE,
 #if SUPPORTED_PERSONALITIES > 1
+       PERSONALITY1_KLONGSIZE,
+#endif
+#if SUPPORTED_PERSONALITIES > 2
+       PERSONALITY2_KLONGSIZE,
+#endif
+};
+
+#if SUPPORTED_PERSONALITIES > 1
+
+const char *const personality_names[] =
+# if defined POWERPC64
+       {"64 bit", "32 bit"}
+# elif defined X86_64
+       {"64 bit", "32 bit", "x32"}
+# elif defined X32
+       {"x32", "32 bit"}
+# elif defined AARCH64
+       {"64 bit", "32 bit"}
+# elif defined TILE
+       {"64-bit", "32-bit"}
+# else
+#  error Add personality names for your achitecture.
+# endif
+       ;
+
 unsigned current_personality;
 
 # ifndef current_wordsize
 unsigned current_wordsize;
-static const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
-       PERSONALITY0_WORDSIZE,
-       PERSONALITY1_WORDSIZE,
-# if SUPPORTED_PERSONALITIES > 2
-       PERSONALITY2_WORDSIZE,
 # endif
-};
+# ifndef current_klongsize
+unsigned current_klongsize;
 # endif
 
 # ifndef current_klongsize
 unsigned current_klongsize;
-static const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
-       PERSONALITY0_KLONGSIZE,
-       PERSONALITY1_KLONGSIZE,
-#  if SUPPORTED_PERSONALITIES > 2
-       PERSONALITY2_KLONGSIZE,
-#  endif
-};
 # endif
 
 void
@@ -307,25 +393,10 @@ update_personality(struct tcb *tcp, unsigned int 
personality)
                return;
        tcp->currpers = personality;
 
-# undef PERSONALITY_NAMES
-# if defined POWERPC64
-#  define PERSONALITY_NAMES {"64 bit", "32 bit"}
-# elif defined X86_64
-#  define PERSONALITY_NAMES {"64 bit", "32 bit", "x32"}
-# elif defined X32
-#  define PERSONALITY_NAMES {"x32", "32 bit"}
-# elif defined AARCH64
-#  define PERSONALITY_NAMES {"64 bit", "32 bit"}
-# elif defined TILE
-#  define PERSONALITY_NAMES {"64-bit", "32-bit"}
-# endif
-# ifdef PERSONALITY_NAMES
        if (!qflag) {
-               static const char *const names[] = PERSONALITY_NAMES;
                error_msg("[ Process PID=%d runs in %s mode. ]",
-                         tcp->pid, names[personality]);
+                         tcp->pid, personality_names[personality]);
        }
-# endif
 }
 #endif
 
@@ -543,40 +614,55 @@ static int arch_set_success(struct tcb *);
 
 struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
 
-static struct inject_opts *
-tcb_inject_opts(struct tcb *tcp)
+static struct inject_values
+tcb_inject_values(struct tcb *tcp, bool step)
 {
-       return (scno_in_range(tcp->scno) && 
tcp->inject_vec[current_personality])
-              ? &tcp->inject_vec[current_personality][tcp->scno] : NULL;
-}
-
-
-static long
-tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
-{
-       if (!tcp->inject_vec[current_personality]) {
+       if (step && !tcp->inject_vec[current_personality] &&
+           inject_vec[current_personality]) {
                tcp->inject_vec[current_personality] =
                        xcalloc(nsyscalls, sizeof(**inject_vec));
                memcpy(tcp->inject_vec[current_personality],
                       inject_vec[current_personality],
                       nsyscalls * sizeof(**inject_vec));
        }
+       struct inject_opts *opts =
+               scno_in_range(tcp->scno) && tcp->inject_vec[current_personality]
+               ? &tcp->inject_vec[current_personality][tcp->scno] : NULL;
+       struct inject_values res = {
+               .rval = INJECT_VALS_RVAL_DEFAULT,
+               .signo = 0,
+       };
+       if (opts) {
+               if (step) {
+                       if (opts->first != 0 && --opts->first == 0) {
+                               res = opts->vals;
+                               opts->first = opts->step;
+                       }
+               } else {
+                       res = opts->vals;
+               }
+       }
+#ifdef USE_LUAJIT
+       if (tcp->flags & TCB_AD_HOC_INJECT) {
+               struct inject_values ad_hoc_vals = tcp->ad_hoc_inject_vals;
+               if (ad_hoc_vals.rval != INJECT_VALS_RVAL_DEFAULT)
+                       res.rval = ad_hoc_vals.rval;
+               if (ad_hoc_vals.signo > 0)
+                       res.signo = ad_hoc_vals.signo;
+       }
+#endif
+       return res;
+}
 
-       struct inject_opts *opts = tcb_inject_opts(tcp);
-
-       if (!opts || opts->first == 0)
-               return 0;
-
-       --opts->first;
-
-       if (opts->first != 0)
-               return 0;
 
-       opts->first = opts->step;
+static long
+tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
+{
+       struct inject_values vals = tcb_inject_values(tcp, true);
 
-       if (opts->signo > 0)
-               *signo = opts->signo;
-       if (opts->rval != INJECT_OPTS_RVAL_DEFAULT && !arch_set_scno(tcp, -1))
+       if (vals.signo > 0)
+               *signo = vals.signo;
+       if (vals.rval != INJECT_VALS_RVAL_DEFAULT && !arch_set_scno(tcp, -1))
                tcp->flags |= TCB_TAMPERED;
 
        return 0;
@@ -585,22 +671,22 @@ tamper_with_syscall_entering(struct tcb *tcp, unsigned 
int *signo)
 static long
 tamper_with_syscall_exiting(struct tcb *tcp)
 {
-       struct inject_opts *opts = tcb_inject_opts(tcp);
+       struct inject_values vals = tcb_inject_values(tcp, false);
 
-       if (!opts)
+       if (vals.rval == INJECT_VALS_RVAL_DEFAULT)
                return 0;
 
-       if (opts->rval >= 0) {
+       if (vals.rval >= 0) {
                kernel_long_t u_rval = tcp->u_rval;
 
-               tcp->u_rval = opts->rval;
+               tcp->u_rval = vals.rval;
                if (arch_set_success(tcp)) {
                        tcp->u_rval = u_rval;
                } else {
                        tcp->u_error = 0;
                }
        } else {
-               unsigned long new_error = -opts->rval;
+               unsigned long new_error = -vals.rval;
 
                if (new_error != tcp->u_error && new_error <= MAX_ERRNO_VALUE) {
                        unsigned long u_error = tcp->u_error;
@@ -663,6 +749,12 @@ syscall_entering_decode(struct tcb *tcp)
        return 1;
 }
 
+static bool
+syscall_ad_hoc_injected(struct tcb *tcp)
+{
+       return (tcp->qual_flg & QUAL_INJECT) && (tcp->flags & 
TCB_AD_HOC_INJECT);
+}
+
 int
 syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
 {
@@ -685,13 +777,13 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
         || (tracing_paths && !pathtrace_match(tcp))
        ) {
                tcp->flags |= TCB_FILTERED;
-               return 0;
+               goto maybe_ad_hoc_tamper;
        }
 
        tcp->flags &= ~TCB_FILTERED;
 
        if (hide_log(tcp)) {
-               return 0;
+               goto maybe_ad_hoc_tamper;
        }
 
        if (tcp->qual_flg & QUAL_INJECT)
@@ -714,6 +806,11 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
                ? printargs(tcp) : tcp->s_ent->sys_func(tcp);
        fflush(tcp->outf);
        return res;
+
+maybe_ad_hoc_tamper:
+       if (syscall_ad_hoc_injected(tcp))
+               tamper_with_syscall_entering(tcp, sig);
+       return 0;
 }
 
 void
@@ -754,21 +851,28 @@ syscall_exiting_decode(struct tcb *tcp, struct timeval 
*ptv)
        }
 #endif
 
-       if (filtered(tcp) || hide_log(tcp))
+       if ((filtered(tcp) || hide_log(tcp))
+        && !(tcp->qual_flg & QUAL_HOOK_EXIT) && !syscall_ad_hoc_injected(tcp))
                return 0;
 
        get_regs(tcp->pid);
 #if SUPPORTED_PERSONALITIES > 1
        update_personality(tcp, tcp->currpers);
 #endif
-       return get_regs_error ? -1 : get_syscall_result(tcp);
+       if (get_regs_error || get_syscall_result(tcp) == -1)
+               return -1;
+
+       if (syserror(tcp) && syscall_tampered(tcp))
+               tamper_with_syscall_exiting(tcp);
+
+       return 1;
 }
 
 int
 syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res)
 {
-       if (syserror(tcp) && syscall_tampered(tcp))
-               tamper_with_syscall_exiting(tcp);
+       if (filtered(tcp) || hide_log(tcp))
+               return 0;
 
        if (cflag) {
                count_syscall(tcp, &tv);
@@ -977,7 +1081,7 @@ syscall_exiting_trace(struct tcb *tcp, struct timeval tv, 
int res)
 void
 syscall_exiting_finish(struct tcb *tcp)
 {
-       tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED);
+       tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED | TCB_AD_HOC_INJECT);
        tcp->sys_func_rval = 0;
        free_tcb_priv_data(tcp);
 }
diff --git a/sysent.h b/sysent.h
index 92de7468..15b83693 100644
--- a/sysent.h
+++ b/sysent.h
@@ -1,13 +1,31 @@
-#ifndef STRACE_SYSENT_H
-#define STRACE_SYSENT_H
+#if !defined(STRACE_SYSENT_H) || defined(FFI_CDEF)
+#ifndef FFI_CDEF
+# define STRACE_SYSENT_H
+#endif
 
+#include "ffi.h"
+
+FFI_CONTENT(
 typedef struct sysent {
        unsigned nargs;
        int     sys_flags;
+)
+/* We don't want to expose sen and sys_func to LuaJIT */
+#ifdef FFI_CDEF
+FFI_CONTENT(
+       int priv1;
+       void *priv2;
+)
+#else
+FFI_CONTENT(
        int     sen;
        int     (*sys_func)();
+)
+#endif
+FFI_CONTENT(
        const char *sys_name;
 } struct_sysent;
+)
 
 #define TRACE_FILE                     00000001        /* Trace file-related 
syscalls. */
 #define TRACE_IPC                      00000002        /* Trace IPC-related 
syscalls. */
@@ -29,4 +47,4 @@ typedef struct sysent {
 #define TRACE_FSTAT                    00400000        /* Trace 
*fstat{,at}{,64} syscalls. */
 #define TRACE_STAT_LIKE                        01000000        /* Trace 
*{,l,f}stat{,x,at}{,64} syscalls. */
 
-#endif /* !STRACE_SYSENT_H */
+#endif /* !defined(STRACE_SYSENT_H) || defined(FFI_CDEF) */
-- 
2.11.0


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Strace-devel mailing list
Strace-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/strace-devel

Reply via email to