The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
machine.c
Go to the documentation of this file.
1 /*
2  * This library is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU Lesser General Public
4  * License as published by the Free Software Foundation; either
5  * version 2.1 of the License, or (at your option) any later version.
6  *
7  * This library is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10  * Lesser General Public License for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public
13  * License along with this library; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /** State machine functions
18  *
19  * @file src/lib/util/machine.c
20  *
21  * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
22  */
23 RCSID("$Id: bd8fcd446cc0672e44e5ae1173274360eaee6685 $")
24 
25 #include "machine.h"
26 
27 typedef struct {
28  fr_machine_state_t const *def; //!< static definition of names, callbacks for this particular state
29  fr_dlist_head_t enter[2]; //!< pre/post enter hooks
30  fr_dlist_head_t process[2]; //!< pre/post process hooks
31  fr_dlist_head_t exit[2]; //!< pre/post exit hooks
32  fr_dlist_head_t signal[2]; //!< pre/post signal hooks
34 
35 typedef struct {
36  int state; //!< state transition to defer
37  fr_dlist_t dlist; //!< linked list of deferred signals
39 
40 /** Hooks
41  *
42  */
43 struct fr_machine_s {
44  fr_machine_def_t const *def; //!< static definition of states, names, callbacks for the state machine
45  void *uctx; //!< to pass to the various handlers
46  fr_machine_state_inst_t *current; //!< current state we are in
47  void const *in_handler; //!< which handler we are in
48 
49  fr_dlist_head_t deferred; //!< list of deferred entries
50  int paused; //!< are transitions paused?
51  bool dead; //!< we were asked to exit, but aren't yet done cleaning up
52 
53  fr_machine_state_inst_t state[]; //!< all of the state transitions
54 };
55 
56 typedef struct {
57  void *uctx; //!< to pass back to the function
58  fr_machine_hook_func_t func; //!< function to call for the hook
59  bool oneshot; //!< is this a one-shot callback?
60  fr_dlist_head_t *head; //!< where the hook is stored
61  fr_dlist_t dlist; //!< linked list of hooks
63 
64 #undef PRE
65 #define PRE (0)
66 #undef POST
67 #define POST (1)
68 
69 /** Call the hook with the current state machine, and the hooks context
70  *
71  * Note that in most cases, the hook will have saved it's own state
72  * machine in uctx, and will not need the current state machine.
73  */
74 static inline void call_hook(fr_machine_t *m, fr_dlist_head_t *head, int state1, int state2)
75 {
76  fr_machine_hook_t *hook, *next;
77 
78  for (hook = fr_dlist_head(head); hook != NULL; hook = next) {
79  next = fr_dlist_next(head, hook);
80 
81  hook->func(m, state1, state2, hook->uctx);
82  if (hook->oneshot) talloc_free(hook);
83  }
84 }
85 
86 /** Transition from one state to another.
87  *
88  * Including calling pre/post hooks.
89  *
90  * None of the functions called from here are allowed to perform a
91  * state transition.
92  */
93 static void state_transition(fr_machine_t *m, int state, void *in_handler)
94 {
96  int old;
97 
98  fr_assert(current != NULL);
99 
100  fr_assert(!m->in_handler);
101  m->in_handler = in_handler;
102 
103  old = current->def->number;
104 
105  /*
106  * Exit the current state.
107  */
108  call_hook(m, &current->exit[PRE], old, state);
109  if (current->def->exit) current->def->exit(m, m->uctx);
110  call_hook(m, &current->exit[POST], old, state);
111 
112  /*
113  * Reset "current", and enter the new state.
114  */
115  current = m->current = &m->state[state];
116 
117  call_hook(m, &current->enter[PRE], old, state);
118  if (current->def->enter) current->def->enter(m, m->uctx);
119  call_hook(m, &current->enter[POST], old, state);
120 
121  /*
122  * We may have transitioned into the "free" state. If
123  * so, mark the state machine as dead.
124  */
125  m->dead = (state == m->def->free);
126 
127  m->in_handler = NULL;
128 }
129 
130 
131 /** Free a state machine
132  *
133  * When a state machine is freed, it will first transition to the
134  * "free" state. That state is presumed to do all appropriate
135  * cleanups.
136  */
138 {
139  fr_assert(m);
140  fr_assert(m->def);
141  fr_assert(m->def->free);
142 
143  /*
144  * Don't transition into the free state multiple times.
145  */
146  if (m->current->def->number == m->def->free) return 0;
147 
148  fr_assert(m->state[m->def->free].enter != NULL);
149 
150  /*
151  * Exit the current state, and enter the free state.
152  */
153  state_transition(m, m->def->free, (void *) _machine_free);
154 
155  /*
156  * Don't call "process" on the free state. Simply
157  * entering the free state _should_ clean everything up.
158  *
159  * Don't check for deferred states. Once we enter the
160  * "free" state, we can't do anything else.
161  */
162 
163  return 0;
164 }
165 
166 /** Instantiate a state machine
167  *
168  * @param ctx the talloc ctx
169  * @param def the definition of the state machine
170  * @param uctx the context passed to the callbacks
171  * @return
172  * - NULL on error
173  * - !NULL state machine which can be used.
174  */
175 fr_machine_t *fr_machine_alloc(TALLOC_CTX *ctx, fr_machine_def_t const *def, void *uctx)
176 {
177  int i, j, next;
178  fr_machine_t *m;
179 
180  /*
181  * We always reserve 0 for "invalid state".
182  *
183  * The "max_state" is the maximum allowed state, which is a valid state number.
184  */
185  m = (fr_machine_t *) talloc_zero_array(ctx, uint8_t, sizeof(fr_machine_t) + sizeof(m->state[0]) * (def->max_state + 1));
186  if (!m) return NULL;
187 
188  talloc_set_type(m, fr_machine_t);
189 
190  *m = (fr_machine_t) {
191  .uctx = uctx,
192  .def = def,
193  };
194 
195  /*
196  * Initialize the instance structures for each of the
197  * states.
198  */
199  for (i = 1; i <= def->max_state; i++) {
200  fr_machine_state_inst_t *state = &m->state[i];
201 
202  state->def = &def->state[i];
203 
204  for (j = 0; j < 2; j++) {
205  fr_dlist_init(&state->enter[j], fr_machine_hook_t, dlist);
206  fr_dlist_init(&state->process[j], fr_machine_hook_t, dlist);
207  fr_dlist_init(&state->exit[j], fr_machine_hook_t, dlist);
208  fr_dlist_init(&state->signal[j], fr_machine_hook_t, dlist);
209  }
210  }
211 
213 
214  /*
215  * Set the current state to "init".
216  */
217  m->current = &m->state[def->init];
218 
219 #ifdef STATIC_ANALYZER
220  if (!m->current || !m->current->def || !m->current->def->process) {
221  talloc_free(m);
222  return NULL;
223  }
224 #endif
225 
226  /*
227  * We don't transition into the "init" state, as there is
228  * no previous state. We just run the "process"
229  * function, which should transition us into a more
230  * permanent state.
231  *
232  * We don't run any pre/post hooks, as the state machine
233  * is new, and no hooks have been added.
234  *
235  * The result of the initialization routine can be
236  * another new state, or 0 for "stay in the current
237  * state".
238  */
239  fr_assert(m->current->def);
240  fr_assert(!m->current->def->enter);
241  fr_assert(!m->current->def->exit);
243 
244  next = m->current->def->process(m, uctx);
245  fr_assert(next >= 0);
246 
247  if (def->free) talloc_set_destructor(m, _machine_free);
248 
250 
251  return m;
252 }
253 
254 /** Post the new state to the state machine.
255  *
256  */
257 static int state_post(fr_machine_t *m, int state)
258 {
259 #ifndef NDEBUG
261 #endif
262 
263  /*
264  * The called function requested that we transition to
265  * the "free" state. Don't do that, but instead return
266  * an error to the caller. The caller MUST do nothing
267  * other than free the state machine.
268  */
269  if (state == m->def->free) {
270  m->dead = true;
271  return -1;
272  }
273 
274  /*
275  * This is an assertion, because the state machine itself
276  * shouldn't be broken.
277  */
278  fr_assert(current->def->allowed[state]);
279 
280  /*
281  * Transition to the new state, and pause the transition if necessary.
282  */
283  fr_machine_transition(m, state);
284 
285  return state;
286 }
287 
288 
289 /** Process the state machine
290  *
291  * @param m The state machine
292  * @return
293  * - 0 for "no transition has occurred"
294  * - >0 for "we are in a new state".
295  * -<0 for "error, you should tear down the state machine".
296  *
297  * This function should be called by the user of the machine.
298  *
299  * In general, the caller doesn't really care about the return code
300  * of this function. The only real utility is >=0 for "continue
301  * calling the state machine as necessary", or <0 for "shut down the
302  * state machine".
303  */
305 {
306  int state, old;
308 
309 redo:
310  current = m->current;
311 
312  /*
313  * Various sanity checks to ensure that the state machine
314  * implementation isn't doing anything crazy.
315  */
316 
317  fr_assert(current != NULL);
318  fr_assert(!m->dead);
319  fr_assert(!m->paused);
320  fr_assert(!m->in_handler);
322 
323  m->in_handler = current;
324  old = current->def->number;
325 
326  call_hook(m, &current->process[PRE], old, old);
327 
328  /*
329  * Entering this state may, in fact, cause us to switch
330  * states. If so, we process the new state, not the old
331  * one
332  */
333  if (fr_dlist_num_elements(&m->deferred) > 0) {
334  m->in_handler = NULL;
336 
337  /*
338  * We do not process dead state machines.
339  */
340  if (m->dead) return m->def->free;
341 
342  /*
343  * Start over with the new "pre" process handler.
344  *
345  * Note that if we have a state transition, we
346  * skip both "process" and "post-process".
347  */
348  goto redo;
349  }
350 
351  state = current->def->process(m, m->uctx);
352 
353  /*
354  * The "process" function CANNOT do state transitions on
355  * its own.
356  */
358 
359  call_hook(m, &current->process[POST], old, old);
360 
361  m->in_handler = NULL;
362 
363  /*
364  * No changes.
365  */
366  if (state == 0) {
367  if (fr_dlist_num_elements(&m->deferred) == 0) return 0;
368 
370  return m->current->def->number;
371  }
372 
373  return state_post(m, state);
374 }
375 
376 /** Transition to a new state
377  *
378  * @param m The state machine
379  * @param state the state to transition to
380  * @return
381  * - <0 for error
382  * - 0 for the transition was made (or deferred)
383  *
384  * The transition MAY be deferred. Note that only one transition at
385  * a time can be deferred.
386  *
387  * This function MUST NOT be called from any "hook", or from any
388  * enter/exit/process function. It should ONLY be called from the
389  * "parent" of the state machine, when it decides that the state
390  * machine needs to change.
391  *
392  * i.e. from a timer, or an IO callback
393  */
395 {
397 
398  if (m->dead) return -1;
399 
400  /*
401  * Bad states are not allowed.
402  */
403  if ((state <= 0) || (state > m->def->max_state)) return -1;
404 
405  /*
406  * If we are not in a state, we cannot transition to
407  * anything else.
408  */
409  if (!current) return -1;
410 
411  /*
412  * Transition to self is "do nothing".
413  */
414  if (current->def->number == state) return 0;
415 
416  /*
417  * Check if the transitions are allowed.
418  */
419  if (!current->def->allowed[state]) return -1;
420 
421  /*
422  * The caller may be mucking with bits of the state
423  * machine and/or the code surrounding the state machine.
424  * In that case, the caller doesn't want transitions to
425  * occur until it's done those changes. Otherwise the
426  * state machine could disappear in the middle of a
427  * function, which is bad.
428  *
429  * However, the rest of the code doesn't know what the
430  * caller wants. So the caller "pauses" state
431  * transitions until it's done. We check for that here,
432  * and defer transitions until such time as the
433  * transitions are resumed.
434  */
435  if (m->in_handler || m->paused) {
436  fr_machine_defer_t *defer = talloc_zero(m, fr_machine_defer_t);
437 
438  if (!defer) return -1;
439 
440  defer->state = state;
441  fr_dlist_insert_tail(&m->deferred, defer);
442  return 0;
443  }
444 
445  /*
446  * We're allowed to do the transition now, so exit the
447  * current state, and enter the new one.
448  */
449  state_transition(m, state, (void *) fr_machine_transition);
450 
451  /*
452  * Entering a state may cause state transitions to occur.
453  * Usually due to pre/post callbacks. If that happens,
454  * then we immediately process the deferred states.
455  */
457 
458  return 0;
459 }
460 
461 /** Get the current state
462  *
463  * @param m The state machine
464  * @return
465  * The current state, or 0 for "not in any state"
466  */
468 {
469  fr_assert(!m->dead);
470 
471  if (!m->current) return 0;
472 
473  return m->current->def->number;
474 }
475 
476 /** Get the name of a particular state
477  *
478  * @param m The state machine
479  * @param state The state to query
480  * @return
481  * the name
482  */
483 char const *fr_machine_state_name(fr_machine_t *m, int state)
484 {
485  fr_assert(!m->dead);
486 
487  if ((state < 0) || (state > m->def->max_state)) return "<INVALID>";
488 
489  if (!state) {
490  if (m->current) {
491  state = m->current->def->number;
492 
493  } else {
494  return "<INVALID>";
495  }
496  }
497 
498  return m->def->state[state].name;
499 }
500 
502 {
503  (void) fr_dlist_remove(hook->head, &hook->dlist);
504 
505  return 0;
506 }
507 
508 
509 /** Add a hook to a state, with an optional talloc_ctx.
510  *
511  * The hook is removed when the talloc ctx is freed.
512  *
513  * You can also remove the hook by freeing the returned pointer.
514  */
515 void *fr_machine_hook(fr_machine_t *m, TALLOC_CTX *ctx, int state_to_hook, fr_machine_hook_type_t type, fr_machine_hook_sense_t sense,
516  bool oneshot, fr_machine_hook_func_t func, void *uctx)
517 {
518  fr_machine_hook_t *hook;
520  fr_machine_state_inst_t *state = &m->state[state_to_hook];
521 
522  fr_assert(!m->dead);
523 
524  switch (type) {
525  case FR_MACHINE_ENTER:
526  head = &state->enter[sense];
527  break;
528 
529  case FR_MACHINE_PROCESS:
530  head = &state->process[sense];
531  break;
532 
533  case FR_MACHINE_EXIT:
534  head = &state->exit[sense];
535  break;
536 
537  case FR_MACHINE_SIGNAL:
538  head = &state->signal[sense];
539  break;
540 
541  default:
542  return NULL;
543  }
544 
545  hook = talloc_zero(ctx, fr_machine_hook_t);
546  if (!hook) return NULL;
547 
548  *hook = (fr_machine_hook_t) {
549  .func = func,
550  .head = head, /* needed for updating num_elements on remove */
551  .uctx = uctx,
552  .oneshot = oneshot,
553  };
554 
556 
557  talloc_set_destructor(hook, _machine_hook_free);
558 
559  return hook;
560 }
561 
562 /** Pause any transitions.
563  *
564  */
566 {
567  fr_assert(!m->dead);
568 
569  m->paused++;
570 }
571 
572 /** Resume transitions.
573  *
574  */
576 {
577  fr_machine_defer_t *defer, *next;
578 
579  fr_assert(!m->dead);
580  fr_assert(!m->in_handler);
581 
582  if (m->paused > 0) {
583  m->paused--;
584  if (m->paused > 0) return;
585  }
586 
587  if (fr_dlist_num_elements(&m->deferred) == 0) return;
588 
589  /*
590  * Process all of the deferred transitions
591  *
592  * Hopefully this process does not cause new state
593  * transitions to occur. Otherwise we might end up in an
594  * infinite loop.
595  */
596  for (defer = fr_dlist_head(&m->deferred); defer != NULL; defer = next) {
597  int state = defer->state;
598 
599  next = fr_dlist_next(&m->deferred, defer);
600  fr_dlist_remove(&m->deferred, defer);
601  talloc_free(defer);
602 
603  state_transition(m, state, (void *) fr_machine_resume);
604  }
605 }
606 
607 /** Send an async signal to the state machine.
608  *
609  * @param m The state machine
610  * @param signal the signal to send to the state machne
611  * @return
612  * - 0 for "no transition has occurred"
613  * - >0 for "we are in a new state".
614  * -<0 for "error, you should tear down the state machine".
615  *
616  * The signal function can return a new state. i.e. some signals get
617  * ignored, and others cause transitions.
618  */
619 int fr_machine_signal(fr_machine_t *m, int signal)
620 {
621  int old, state;
623 
624  if (m->dead) return -1;
625 
626  /*
627  * Bad signals are not allowed.
628  */
629  if ((signal <= 0) || (signal > m->def->max_signal)) return -1;
630 
631  /*
632  * Can't send an async signal from within a handler.
633  */
634  if (m->in_handler) return -1;
635 
636  m->in_handler = (void *) fr_machine_signal;
637  old = current->def->number;
638  state = 0;
639 
640  /*
641  * Note that the callbacks (for laziness) take the
642  * _current_ state, and the _signal_. Not the _new_
643  * state!
644  */
645  call_hook(m, &current->signal[PRE], old, signal);
646  if (current->def->signal) state = current->def->signal(m, signal, m->uctx);
647  call_hook(m, &current->signal[POST], old, signal);
648 
649  m->in_handler = NULL;
650 
651  /*
652  * No changes. Tell the caller to wait for something
653  * else to signal a transition.
654  */
655  if (state == 0) return 0;
656 
657  fr_assert(state != old); /* can't ask us to transition to the current state */
658 
659  return state_post(m, state);
660 }
661 
#define RCSID(id)
Definition: build.h:481
next
Definition: dcursor.h:178
fr_dcursor_eval_t void const * uctx
Definition: dcursor.h:546
fr_dcursor_iter_t void * current
Definition: dcursor.h:148
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition: dlist.h:260
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition: dlist.h:555
static unsigned int fr_dlist_num_elements(fr_dlist_head_t const *head)
Return the number of elements in the dlist.
Definition: dlist.h:939
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition: dlist.h:486
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition: dlist.h:378
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition: dlist.h:638
Head of a doubly linked list.
Definition: dlist.h:51
Entry in a doubly linked list.
Definition: dlist.h:41
talloc_free(reap)
static void call_hook(fr_machine_t *m, fr_dlist_head_t *head, int state1, int state2)
Call the hook with the current state machine, and the hooks context.
Definition: machine.c:74
fr_dlist_t dlist
linked list of deferred signals
Definition: machine.c:37
fr_dlist_head_t signal[2]
pre/post signal hooks
Definition: machine.c:32
int fr_machine_signal(fr_machine_t *m, int signal)
Send an async signal to the state machine.
Definition: machine.c:619
fr_dlist_head_t * head
where the hook is stored
Definition: machine.c:60
static int _machine_free(fr_machine_t *m)
Free a state machine.
Definition: machine.c:137
fr_dlist_t dlist
linked list of hooks
Definition: machine.c:61
fr_machine_state_t const * def
static definition of names, callbacks for this particular state
Definition: machine.c:28
fr_dlist_head_t enter[2]
pre/post enter hooks
Definition: machine.c:29
#define PRE
Definition: machine.c:65
static int _machine_hook_free(fr_machine_hook_t *hook)
Definition: machine.c:501
bool dead
we were asked to exit, but aren't yet done cleaning up
Definition: machine.c:51
int fr_machine_process(fr_machine_t *m)
Process the state machine.
Definition: machine.c:304
fr_machine_hook_func_t func
function to call for the hook
Definition: machine.c:58
static void state_transition(fr_machine_t *m, int state, void *in_handler)
Transition from one state to another.
Definition: machine.c:93
void * uctx
to pass to the various handlers
Definition: machine.c:45
fr_machine_state_inst_t state[]
all of the state transitions
Definition: machine.c:53
int state
state transition to defer
Definition: machine.c:36
bool oneshot
is this a one-shot callback?
Definition: machine.c:59
char const * fr_machine_state_name(fr_machine_t *m, int state)
Get the name of a particular state.
Definition: machine.c:483
fr_machine_def_t const * def
static definition of states, names, callbacks for the state machine
Definition: machine.c:44
fr_dlist_head_t process[2]
pre/post process hooks
Definition: machine.c:30
fr_dlist_head_t deferred
list of deferred entries
Definition: machine.c:49
void fr_machine_resume(fr_machine_t *m)
Resume transitions.
Definition: machine.c:575
int fr_machine_transition(fr_machine_t *m, int state)
Transition to a new state.
Definition: machine.c:394
#define POST
Definition: machine.c:67
void * fr_machine_hook(fr_machine_t *m, TALLOC_CTX *ctx, int state_to_hook, fr_machine_hook_type_t type, fr_machine_hook_sense_t sense, bool oneshot, fr_machine_hook_func_t func, void *uctx)
Add a hook to a state, with an optional talloc_ctx.
Definition: machine.c:515
fr_dlist_head_t exit[2]
pre/post exit hooks
Definition: machine.c:31
void fr_machine_pause(fr_machine_t *m)
Pause any transitions.
Definition: machine.c:565
int fr_machine_current(fr_machine_t *m)
Get the current state.
Definition: machine.c:467
fr_machine_t * fr_machine_alloc(TALLOC_CTX *ctx, fr_machine_def_t const *def, void *uctx)
Instantiate a state machine.
Definition: machine.c:175
int paused
are transitions paused?
Definition: machine.c:50
void const * in_handler
which handler we are in
Definition: machine.c:47
void * uctx
to pass back to the function
Definition: machine.c:57
static int state_post(fr_machine_t *m, int state)
Post the new state to the state machine.
Definition: machine.c:257
fr_machine_state_inst_t * current
current state we are in
Definition: machine.c:46
Hooks.
Definition: machine.c:43
int max_signal
1..max signals are permitted
Definition: machine.h:54
struct fr_machine_s fr_machine_t
Definition: machine.h:30
fr_machine_hook_type_t
Definition: machine.h:76
@ FR_MACHINE_PROCESS
Definition: machine.h:78
@ FR_MACHINE_SIGNAL
Definition: machine.h:80
@ FR_MACHINE_ENTER
Definition: machine.h:77
@ FR_MACHINE_EXIT
Definition: machine.h:79
int free
state to run on free
Definition: machine.h:56
fr_machine_process_t process
run this to process the current state
Definition: machine.h:44
fr_machine_func_t exit
run this when exiting the state
Definition: machine.h:45
int number
enum for this state machine
Definition: machine.h:42
char const * name
state name
Definition: machine.h:41
void(* fr_machine_hook_func_t)(fr_machine_t *m, int, int, void *uctx)
Definition: machine.h:38
fr_machine_state_t state[]
states
Definition: machine.h:57
int init
state to run on init
Definition: machine.h:55
int max_state
1..max states are permitted
Definition: machine.h:53
fr_machine_hook_sense_t
Definition: machine.h:83
fr_machine_func_t enter
run this when entering the state
Definition: machine.h:43
unsigned char uint8_t
Definition: merged_model.c:30
fr_assert(0)
fr_aka_sim_id_type_t type
static fr_slen_t head
Definition: xlat.h:406