/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, ¤t->exit[PRE], old, state); |
109 | 0 | if (current->def->exit) current->def->exit(m, m->uctx); |
110 | 0 | call_hook(m, ¤t->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, ¤t->enter[PRE], old, state); |
118 | 0 | if (current->def->enter) current->def->enter(m, m->uctx); |
119 | 0 | call_hook(m, ¤t->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, ¤t->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, ¤t->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, ¤t->signal[PRE], old, signal); |
646 | 0 | if (current->def->signal) state = current->def->signal(m, signal, m->uctx); |
647 | 0 | call_hook(m, ¤t->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 | | |