Coverage Report

Created: 2025-12-14 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/machine.c
Line
Count
Source
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
33
} fr_machine_state_inst_t;
34
35
typedef struct {
36
  int     state;    //!< state transition to defer
37
  fr_dlist_t    dlist;    //!< linked list of deferred signals
38
} fr_machine_defer_t;
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
62
} fr_machine_hook_t;
63
64
#undef PRE
65
0
#define PRE (0)
66
#undef POST
67
0
#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
0
{
76
0
  fr_machine_hook_t *hook, *next;
77
78
0
  for (hook = fr_dlist_head(head); hook != NULL; hook = next) {
79
0
    next = fr_dlist_next(head, hook);
80
81
0
    hook->func(m, state1, state2, hook->uctx);
82
0
    if (hook->oneshot) talloc_free(hook);
83
0
  }
84
0
}
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
0
{
95
0
  fr_machine_state_inst_t *current = m->current;
96
0
  int old;
97
98
0
  fr_assert(current != NULL);
99
100
0
  fr_assert(!m->in_handler);
101
0
  m->in_handler = in_handler;
102
103
0
  old = current->def->number;
104
105
  /*
106
   *  Exit the current state.
107
   */
108
0
  call_hook(m, &current->exit[PRE], old, state);
109
0
  if (current->def->exit) current->def->exit(m, m->uctx);
110
0
  call_hook(m, &current->exit[POST], old, state);
111
112
  /*
113
   *  Reset "current", and enter the new state.
114
   */
115
0
  current = m->current = &m->state[state];
116
117
0
  call_hook(m, &current->enter[PRE], old, state);
118
0
  if (current->def->enter) current->def->enter(m, m->uctx);
119
0
  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
0
  m->dead = (state == m->def->free);
126
127
0
  m->in_handler = NULL;
128
0
}
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
 */
137
static int _machine_free(fr_machine_t *m)
138
0
{
139
0
  fr_assert(m);
140
0
  fr_assert(m->def);
141
0
  fr_assert(m->def->free);
142
143
  /*
144
   *  Don't transition into the free state multiple times.
145
   */
146
0
  if (m->current->def->number == m->def->free) return 0;
147
148
0
  fr_assert(m->state[m->def->free].enter != NULL);
149
150
  /*
151
   *  Exit the current state, and enter the free state.
152
   */
153
0
  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
0
  return 0;
164
0
}
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
0
{
177
0
  int i, j, next;
178
0
  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
0
  m = (fr_machine_t *) talloc_zero_array(ctx, uint8_t, sizeof(fr_machine_t) + sizeof(m->state[0]) * (def->max_state + 1));
186
0
  if (!m) return NULL;
187
188
0
  talloc_set_type(m, fr_machine_t);
189
190
0
  *m = (fr_machine_t) {
191
0
    .uctx = uctx,
192
0
    .def = def,
193
0
  };
194
195
  /*
196
   *  Initialize the instance structures for each of the
197
   *  states.
198
   */
199
0
  for (i = 1; i <= def->max_state; i++) {
200
0
    fr_machine_state_inst_t *state = &m->state[i];
201
202
0
    state->def = &def->state[i];
203
204
0
    for (j = 0; j < 2; j++) {
205
0
      fr_dlist_init(&state->enter[j], fr_machine_hook_t, dlist);
206
0
      fr_dlist_init(&state->process[j], fr_machine_hook_t, dlist);
207
0
      fr_dlist_init(&state->exit[j], fr_machine_hook_t, dlist);
208
0
      fr_dlist_init(&state->signal[j], fr_machine_hook_t, dlist);
209
0
    }
210
0
  }
211
212
0
  fr_dlist_init(&m->deferred, fr_machine_defer_t, dlist);
213
214
  /*
215
   *  Set the current state to "init".
216
   */
217
0
  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
0
  fr_assert(m->current->def);
240
0
  fr_assert(!m->current->def->enter);
241
0
  fr_assert(!m->current->def->exit);
242
0
  fr_assert(m->current->def->process);
243
244
0
  next = m->current->def->process(m, uctx);
245
0
  fr_assert(next >= 0);
246
247
0
  if (def->free) talloc_set_destructor(m, _machine_free);
248
249
0
  if (next) fr_machine_transition(m, next);
250
251
0
  return m;
252
0
}
253
254
/** Post the new state to the state machine.
255
 *
256
 */
257
static int state_post(fr_machine_t *m, int state)
258
0
{
259
0
#ifndef NDEBUG
260
0
  fr_machine_state_inst_t *current = m->current;
261
0
#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
0
  if (state == m->def->free) {
270
0
    m->dead = true;
271
0
    return -1;
272
0
  }
273
274
  /*
275
   *  This is an assertion, because the state machine itself
276
   *  shouldn't be broken.
277
   */
278
0
  fr_assert(current->def->allowed[state]);
279
280
  /*
281
   *  Transition to the new state, and pause the transition if necessary.
282
   */
283
0
  fr_machine_transition(m, state);
284
285
0
  return state;
286
0
}
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
 */
304
int fr_machine_process(fr_machine_t *m)
305
0
{
306
0
  int state, old;
307
0
  fr_machine_state_inst_t *current;
308
309
0
redo:
310
0
  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
0
  fr_assert(current != NULL);
318
0
  fr_assert(!m->dead);
319
0
  fr_assert(!m->paused);
320
0
  fr_assert(!m->in_handler);
321
0
  fr_assert(fr_dlist_num_elements(&m->deferred) == 0);
322
323
0
  m->in_handler = current;
324
0
  old = current->def->number;
325
326
0
  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
0
  if (fr_dlist_num_elements(&m->deferred) > 0) {
334
0
    m->in_handler = NULL;
335
0
    fr_machine_resume(m);
336
337
    /*
338
     *  We do not process dead state machines.
339
     */
340
0
    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
0
    goto redo;
349
0
  }
350
351
0
  state = current->def->process(m, m->uctx);
352
353
  /*
354
   *  The "process" function CANNOT do state transitions on
355
   *  its own.
356
   */
357
0
  fr_assert(fr_dlist_num_elements(&m->deferred) == 0);
358
359
0
  call_hook(m, &current->process[POST], old, old);
360
361
0
  m->in_handler = NULL;
362
363
  /*
364
   *  No changes.
365
   */
366
0
  if (state == 0) {
367
0
    if (fr_dlist_num_elements(&m->deferred) == 0) return 0;
368
369
0
    fr_machine_resume(m);
370
0
    return m->current->def->number;
371
0
  }
372
373
0
  return state_post(m, state);
374
0
}
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
 */
394
int fr_machine_transition(fr_machine_t *m, int state)
395
0
{
396
0
  fr_machine_state_inst_t *current = m->current;
397
398
0
  if (m->dead) return -1;
399
400
  /*
401
   *  Bad states are not allowed.
402
   */
403
0
  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
0
  if (!current) return -1;
410
411
  /*
412
   *  Transition to self is "do nothing".
413
   */
414
0
  if (current->def->number == state) return 0;
415
416
  /*
417
   *  Check if the transitions are allowed.
418
   */
419
0
  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
0
  if (m->in_handler || m->paused) {
436
0
    fr_machine_defer_t *defer = talloc_zero(m, fr_machine_defer_t);
437
438
0
    if (!defer) return -1;
439
440
0
    defer->state = state;
441
0
    fr_dlist_insert_tail(&m->deferred, defer);
442
0
    return 0;
443
0
  }
444
445
  /*
446
   *  We're allowed to do the transition now, so exit the
447
   *  current state, and enter the new one.
448
   */
449
0
  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
   */
456
0
  if (fr_dlist_num_elements(&m->deferred) > 0) fr_machine_resume(m);
457
458
0
  return 0;
459
0
}
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
 */
467
int fr_machine_current(fr_machine_t *m)
468
0
{
469
0
  fr_assert(!m->dead);
470
471
0
  if (!m->current) return 0;
472
473
0
  return m->current->def->number;
474
0
}
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
0
{
485
0
  fr_assert(!m->dead);
486
487
0
  if ((state < 0) || (state > m->def->max_state)) return "<INVALID>";
488
489
0
  if (!state) {
490
0
    if (m->current) {
491
0
      state = m->current->def->number;
492
493
0
    } else {
494
0
      return "<INVALID>";
495
0
    }
496
0
  }
497
498
0
  return m->def->state[state].name;
499
0
}
500
501
static int _machine_hook_free(fr_machine_hook_t *hook)
502
0
{
503
0
  (void) fr_dlist_remove(hook->head, &hook->dlist);
504
505
0
  return 0;
506
0
}
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
0
{
518
0
  fr_machine_hook_t *hook;
519
0
  fr_dlist_head_t *head;
520
0
  fr_machine_state_inst_t *state = &m->state[state_to_hook];
521
522
0
  fr_assert(!m->dead);
523
524
0
  switch (type) {
525
0
  case FR_MACHINE_ENTER:
526
0
    head = &state->enter[sense];
527
0
    break;
528
529
0
  case FR_MACHINE_PROCESS:
530
0
    head = &state->process[sense];
531
0
    break;
532
533
0
  case FR_MACHINE_EXIT:
534
0
    head = &state->exit[sense];
535
0
    break;
536
537
0
  case FR_MACHINE_SIGNAL:
538
0
    head = &state->signal[sense];
539
0
    break;
540
541
0
  default:
542
0
    return NULL;
543
0
  }
544
545
0
  hook = talloc_zero(ctx, fr_machine_hook_t);
546
0
  if (!hook) return NULL;
547
548
0
  *hook = (fr_machine_hook_t) {
549
0
    .func = func,
550
0
    .head = head, /* needed for updating num_elements on remove */
551
0
    .uctx = uctx,
552
0
    .oneshot = oneshot,
553
0
  };
554
555
0
  fr_dlist_insert_tail(head, &hook->dlist);
556
557
0
  talloc_set_destructor(hook, _machine_hook_free);
558
559
0
  return hook;
560
0
}
561
562
/** Pause any transitions.
563
 *
564
 */
565
void fr_machine_pause(fr_machine_t *m)
566
0
{
567
0
  fr_assert(!m->dead);
568
569
0
  m->paused++;
570
0
}
571
572
/** Resume transitions.
573
 *
574
 */
575
void fr_machine_resume(fr_machine_t *m)
576
0
{
577
0
  fr_machine_defer_t *defer, *next;
578
579
0
  fr_assert(!m->dead);
580
0
  fr_assert(!m->in_handler);
581
582
0
  if (m->paused > 0) {
583
0
    m->paused--;
584
0
    if (m->paused > 0) return;
585
0
  }
586
587
0
  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
0
  for (defer = fr_dlist_head(&m->deferred); defer != NULL; defer = next) {
597
0
    int state = defer->state;
598
599
0
    next = fr_dlist_next(&m->deferred, defer);
600
0
    fr_dlist_remove(&m->deferred, defer);
601
0
    talloc_free(defer);
602
603
0
    state_transition(m, state, (void *) fr_machine_resume);
604
0
  }
605
0
}
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
0
{
621
0
  int old, state;
622
0
  fr_machine_state_inst_t *current = m->current;
623
624
0
  if (m->dead) return -1;
625
626
  /*
627
   *  Bad signals are not allowed.
628
   */
629
0
  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
0
  if (m->in_handler) return -1;
635
636
0
  m->in_handler = (void *) fr_machine_signal;
637
0
  old = current->def->number;
638
0
  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
0
  call_hook(m, &current->signal[PRE], old, signal);
646
0
  if (current->def->signal) state = current->def->signal(m, signal, m->uctx);
647
0
  call_hook(m, &current->signal[POST], old, signal);
648
649
0
  m->in_handler = NULL;
650
651
  /*
652
   *  No changes.  Tell the caller to wait for something
653
   *  else to signal a transition.
654
   */
655
0
  if (state == 0) return 0;
656
657
0
  fr_assert(state != old); /* can't ask us to transition to the current state */
658
659
0
  return state_post(m, state);
660
0
}
661