cfsm - finite state machine code generator
I have been playing around with replacing or renovating parts of OpenSSH’s sshd server, especially replacing the hand-crafted (and brittle) mainloop with Niels’ libevent. While playing with this on the bus, it occurred to me that several large chunks of the application would be more clear if the state machines they implemented were more explicit. The privilege separation monitor and the channels system (port forwarding, login session, etc.) are good examples of this.
Because there are several places I’d like to do this, I wrote a small code generator to help: cfsm. cfsm takes a simple text state machine specification and outputs a C source and header file and optionally a graphviz dot file for plotting (see below). The code it generates includes transition checking, so it should be impossible to make any transition not expliclty defined. It also allows for the definition of precondition callbacks that muct be satisfied before a transition is allowed. A small example:
state T1a
initial-state
next-state T2
onexit-func do_something
state T1b
initial-state
next-state T2
state T2
next-state T3
exit-precondition is_bottom_half_of_the_hour
state T3
next-state T2
next-state T3
next-state T4
state T4
entry-precondition am_i_in_a_good_mood
This produces the following FSM API: (comments abridged)
enum state { T1a = 0, T1b = 1, T2 = 2, T3 = 3, T4 = 4, };
struct fsm;
struct fsm *state_init(enum state initial_state,
char *errbuf, size_t errlen); /* Allocate a FSM */
void state_free(struct fsm *fsm); /* Free a FSM */
int state_advance(struct fsm *fsm, enum state new_state,
char *errbuf, size_t errlen); /* Move to a new state */
const char *state_ntop(enum state); /* State number -> name */
const char *state_ntop_safe(enum state); /* Same, but doesn't return NULL */
enum state state_current(struct fsm *fsm); /* Returns the current state */
The user of the API will need to provide the callback functions:
/* Preconditions - must return 0 on success */ int is_bottom_half_of_the_hour(enum state current_state, enum state new_state); int am_i_in_a_good_mood(enum state current_state, enum state new_state); /* Transition function */ int do_something(enum state current_state, enum state new_state);
In case you are wondering what the graphviz output looks like, here is an example:
(yes, I know I am abusing standard notation)
This is all very basic stuff, but I think that having simple tools like this around saves time (well, when amortised over their lifespans) and reduces mistakes. The code for cfsm is in anonymous CVS (:ext:anoncvs@anoncvs.mindrot.org/cvs via SSH) and is browsable via cvsweb. If anyone is interested I can make a tarball release of it - just let me know via email.