The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
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 */
23RCSID("$Id: bd8fcd446cc0672e44e5ae1173274360eaee6685 $")
24
25#include "machine.h"
26
27typedef 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
35typedef struct {
36 int state; //!< state transition to defer
37 fr_dlist_t dlist; //!< linked list of deferred signals
39
40/** Hooks
41 *
42 */
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
56typedef 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 */
74static 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 */
93static void state_transition(fr_machine_t *m, int state, void *in_handler)
94{
96 int old;
97
98 fr_assert(current != NULL);
99
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 */
175fr_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
249 if (next) fr_machine_transition(m, next);
250
251 return m;
252}
253
254/** Post the new state to the state machine.
255 *
256 */
257static 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
309redo:
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);
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 */
483char 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 */
515void *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
530 head = &state->process[sense];
531 break;
532
533 case FR_MACHINE_EXIT:
534 head = &state->exit[sense];
535 break;
536
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);
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 */
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:483
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:260
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 void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:638
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 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_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:555
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
char const * fr_machine_state_name(fr_machine_t *m, int state)
Get the name of a particular state.
Definition machine.c:483
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
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
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 fr_machine_transition(fr_machine_t *m, int state)
Transition to a new state.
Definition machine.c:394
#define POST
Definition machine.c:67
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
int paused
are transitions paused?
Definition machine.c:50
void const * in_handler
which handler we are in
Definition machine.c:47
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
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
#define fr_assert(_expr)
Definition rad_assert.h:38
static rc_request_t * current
fr_aka_sim_id_type_t type
static fr_slen_t head
Definition xlat.h:422