7.2. Abitrary Hooks

Building a callout API makes sense for common, structured features in software, but occassionlly there is a need to provide somewhat arbitrary hook points after the software is designed and the noit_hooks system is what satisfies this need.

The design goals here are somewhat specific in that we would like to allow for a large number of hook points at low cost when not instrumented. As such, a hash lookup of registered hooks would be considered to expensive. Additionally, we want to provide strong, compile-time type safety as it can be all to easy to hook something with a function with a slightly incorrect protoype that could result in disasterous corruption or crashes (or perhaps worse daftly subtle bugs that are punishing to troubleshoot).

The hooks system is simple a set of two macros; one allowing for the declaration of function prototypes for registering and invoking a specific programmer-specifiec instrumentation point and the other providing an implementation of the registration and invocation routines. Due to the nature of C, the macro calling conventions are less than elegant, but ultimately require no complicated implementation by the programmer.

7.2.1. Hook Declaration

Declaring hooks is done by calling the NOIT_HOOK_PROTO macro with the name of the hook (a term that composes a valid C function name), the arguments it expects, the type of closure (usually a void *), and some variations on those themes that provide CPP enough info to construct an implementation with no programmer "programming."

The declaration of a hook "foo" will yield in two functions: foo_hook_invoke and foo_hook_register.

Example 7.1. Declaring a foobarquux hook in a header.

A foobarquux hook prototype that takes a struct timeval * argument.

     NOIT_HOOK_PROTO(foobarquux, (struct timeval *now),
                     void *, closure, (void *closure, struct timeval *now));
   

Example 7.2. Implementing a foobarquux hook in source.

A foobarquux hok implementation that takes a struct timeval * argument.

     NOIT_HOOK_IMPL(foobarquux, (struct timeval *now),
                    void *, closure, (void *closure, struct timeval *now),
                    (closure,now));
   

7.2.2. Hook Usage

Once the hook is implemented, it can be used by the application and instrumented by code at runtime. In the below example, we'll invoke the foobarquux instrumentation and assuming no issues arise, we'll invoke the original foobarquux_work() function.

Example 7.3. Instrumenting a function conditionally.

Before we instrument, suppose we have:

     foobarquux_work();
   

Now we wish to allow programmers to add instrumentation immediately before this code that can conditionally prevent its executation:

     struct timeval now;
     gettimeofday(&now, NULL);
     if(NOIT_HOOK_CONTINUE ==
        foobarquux_hook_invoke(&now))
       foobarquux_work();
   

If the hook should not conditionally cause or prevent code to run, the invoke function's return value can be ignored.

In order to register a function that allows the above execution on every other subsequent execution one would provide the following:

Example 7.4. A sample instrumentation of foobarquux

     static my_sample_hook(void *closure, struct timeval *now) {
       static int alt = 0;
       return (alt++ % 2) ? NOIT_HOOK_CONTINUE : NOIT_HOOK_DONE;
     }

     ...
       foobarquux_hook_register("sample", my_sample_hook, NULL);
     ...