docs · development(9)
Development
How to implement and register a new trigger, including its lifecycle, execution contract, calling-context rules, parameter handling and testing requirements.
This page documents the contract a new trigger must follow. It assumes familiarity with LKM development and basic kernel subsystems.
Responsibilities
| component | owns |
|---|---|
| core | module init/teardown, parameter validation for
EXEC, deferred execution via workqueue, invoking
call_usermodehelper() |
| trigger | detecting one condition; deciding whether to call
wrong8007_activate(); never executing anything itself |
Triggers are intentionally stateless or minimally stateful: the keyboard trigger's match counter and the network trigger's heartbeat timestamp are the only meaningful per-trigger state in the current source and both are guarded by a spinlock.
struct wrong8007_trigger
struct wrong8007_trigger {
const char *name;
int (*init)(void);
void (*exit)(void);
};
include/wrong8007.h
| field | contract |
|---|---|
init() | Must return 0 on success or a negative errno on failure. Called once per module load, in array order. Should validate its own module parameters and refuse to proceed on malformed input rather than guessing at intent. |
exit() | Must be safe to call even if
init() partially failed or was never called for this trigger
(e.g. because an earlier trigger's init() failed first). Called
once at module unload and during unwind after a failed load. |
Triggers must not call wrong8007_activate() from either
init() or exit(); only from a live callback
(notifier, hook, timer) after successful initialization.
Registering a trigger
Add the trigger's exported symbol to the core's static array:
extern struct wrong8007_trigger example_trigger;
static struct wrong8007_trigger *triggers[] = {
&keyboard_trigger,
&usb_trigger,
&network_trigger,
&example_trigger,
};
core/wrong8007.c
On load failure partway through this array, the core calls exit()
for every trigger whose init() already succeeded, in reverse order,
before returning the error from module_init.
Scheduling execution
Triggers must not call call_usermodehelper() or anything
execution-related directly. The only sanctioned path from a trigger callback to
execution is:
wrong8007_activate();
This keeps execution context and ordering entirely inside the core, regardless of which trigger fired or what context its callback runs in.
Calling-context rules
wrong8007_activate()is safe to call from process context (keyboard,usb) or softirq context (network's netfilter callback); it performs a single atomic compare-and-swap and, at most, aschedule_work()call, neither of which sleeps.- A trigger's
init()/exit()run in process context (module load/unload) and may sleep; allocation withGFP_KERNELis fine there, as seen inkeyboard.c'skstrdup()of the phrase parameter. - A trigger's live callback must not sleep if it can run in atomic or softirq
context. The network trigger's
nf_hook_fnnever blocks; all header access is preceded bypskb_may_pull()and pointers are re-read after each pull rather than assumed stable. - Avoid allocation in hot paths. The keyboard trigger's per-keystroke work is a table lookup and a byte compare under a spinlock; the network trigger's per-packet work is bounded header parsing with no allocation.
Parameter handling
Triggers may define module parameters, but must follow these rules:
- Validate them inside
init(), not lazily on first use. - Return an error for invalid configuration.
- Do not silently reinterpret malformed configuration.
- Treat parameters as read-only after initialization.
- Prefer strict parsing over permissive behavior.
- Allocate derived state during initialization rather than repeatedly parsing configuration in hot paths.
- Release all trigger-owned allocations during teardown.
Example:
if (!param || !*param)
return -EINVAL;
Logging
Use the project's logging macros consistently:
| macro | use |
|---|---|
wb_dbg | Development-only debug output, compiled in
with -DDEBUG. |
wb_info | Initialization and lifecycle events (module loaded/unloaded, trigger initialized). |
wb_warn | Recoverable or potentially unsafe conditions. |
wb_err | Initialization or runtime errors. |
Avoid logging sensitive configuration values unless there is a clear debugging reason to do so.
Testing new triggers
Recommended workflow:
- Load the module with only the new trigger's parameters set (preferably).
- Verify successful initialization.
- Verify invalid configuration causes initialization failure.
- Verify
exit()unregisters all resources cleanly. - Verify activation in isolation before combining triggers.
- Confirm
rmmodunloads cleanly (make remove), with no leaked memory or dangling notifier registration. - Verify repeated and concurrent activation attempts preserve one-shot behavior.
- Combine the new trigger with existing triggers only after its isolated behavior is understood.
A trigger should be tested both for the condition that activates it and for the conditions that must not activate it.
Code style
Follow Linux kernel coding conventions.
Prefer:
- Well-defined ownership,
- Clear control flow,
- Strict validation,
- No coupling between triggers,
- Comments for non-obvious kernel behavior.
A trigger should be tested both for the condition that activates it and for the conditions that must not activate it.
If a trigger is hard to reason about in isolation, it is not ready to be included yet.