Subject: generic hook infrastructure
To: None <tech-kern@netbsd.org>
From: Brett Lymn <blymn@baesystems.com.au>
List: tech-kern
Date: 01/31/2006 23:27:51
Folks,

The following is based on an observation made by Chuck Silvers, I have
tried to implement a framework to redress the problem.

At the moment we have a lot of hooks in the kernel, mountroot hooks,
power hooks, exec hooks, umount hooks... and many more.  Some of these
hooks are implemented using routines in kern_subr.c but others are
separate implementations.  This means quite a bit of duplicated code
to walk a tailq list and run functions on that list.  What I would
like to propose is a generic hook framework that replaces all the
current hook instances with an opaque hook type that can be
manipulated by the following interface:

hook_list_t *hook_alloc(void) creates and returns a new hook list

void hook_free(hook_list_t *) destroys the given hook list

hook_t *hook_register(hook_list_t *, void *, void *) register a
       callback on a given hook list, the arguments are the hook list,
       the callback function and the callback arguments.  The callback
       function is a function that takes a single argument pointer.
       hook_register() returns a pointer to the entry on the hook list.

void hook_unregister(hook_list_t *, hook_t *) remove the given hook
       from the hook list.

void hook_run(hook_list_t *, int, hook_mask_func_t, void *) run the
       hook functions contained in the given hook list.  Arguments are
       the hook list, a flag to indicate whether or not the hook list
       entries should be removed after running, a mask function and
       mask function arguments.  The mask function can be NULL, if it
       is non null then the function is run with two arguments, the
       hook arguments (set up when registering) and the mask
       arguments, if the mask function returns non-zero then the
       associated hook will be run, a 0 will mean the hook is
       skipped.  This give a mechanism to selectively "gate" hooks.

void hook_proc_run(hook_list_t *, int, void *, hook_mask_func_t, void *)
       This is functionally the same as hook_run() apart from an extra
       argument which is passed to the registered hooks - the hook
       functions on this hook list must accept two arguments instead
       of the normal single argument.  The name of this function
       probably should change...

void hook_run_reverse(hook_list_t *, int, hook_mask_func_t, void *) is
       the same as hook_run() except the hook list is traversed in
       reverse order.  Normally the hooks would be run in the order
       they were added to the list, hook_run_reverse() will start with
       the last hook added and work up the list in reverse order.


I believe that the above functions will cover the functionality
required by all the current hook lists in the kernel.  There may be a
bit of rototill in places to fit some of the current hook functions
into the framework (the fork hooks are an example that will need to be
modified).  One thing that, on reflection, I should add is some
locking to protect the list manipulations - I shall do that.

Does anyone have any comments or suggestions?  

Below is the full header (kern_hook.h) without the copyright stuff:


#ifndef KERN_HOOK_H
#define KERN_HOOK_H

#include <sys/queue.h>

/* Abstract the values of the flag for hook_run and hook_run_reverse */
#define HOOK_PRESERVE   0        /* leave list intact */
#define HOOK_UNREGISTER 1        /* remove hooks from list as they are run */

/* hook callback function, called with "hookarg" */
typedef void (*hook_callback_t)(void *);
typedef void (*hook_callback_proc_t)(void * /*proc*/, void * /*hook_arg*/)

/*
 * hook masking function - if defined and returns 1 then run the hook,
 * called with pointer to hook_arg and mask_arg
 */
typedef int (*hook_mask_func_t)(void * /*hook_arg*/, void * /*mask_arg*/);

struct hook_desc {
        TAILQ_ENTRY(hook_desc) entries;
        void    *hook_func;
        void    *hook_arg;
};

typedef struct hook_desc hook_t;
typedef TAILQ_HEAD(, hook_desc) hook_list_t;


/* allocate a new hook context, returns "context" */
hook_list_t *hook_alloc(void);

/* frees all the state in the given hook list */
void hook_free(hook_list_t *);

/* registers a new callback, returns "hook" */
hook_t *hook_register(hook_list_t *, void *, void *);

/* unregisters a callback */
void hook_unregister(hook_list_t *, hook_t *);

/* calls the callbacks, optionally unregistering the callbacks and optionally
 * applying a masking function (iff not null and function returns 1 then run
 * the hook otherwise skip to next element)
 */
void hook_run(hook_list_t *, int, hook_mask_func_t, void *);

/*
 * Do the same as hook_run except the callback is passed the given
 * argument instead of using the one set when the hook was registered.
 */
void hook_proc_run(hook_list_t *, int, void *, hook_mask_func_t, void *);

/*
 * calls the callbacks in reverse registration order, optionally
 * unregistering the callbacks and optionally applying a masking function
 * (see hook_run above for description)
 */
void hook_run_reverse(hook_list_t *, int, hook_mask_func_t, void *);

#endif

-- 
Brett Lymn