• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/uqmi/uqmid/osmocom/fsm.c

  1 /*! \file fsm.c
  2  * Osmocom generic Finite State Machine implementation. */
  3 /*
  4  * (C) 2016-2019 by Harald Welte <laforge@gnumonks.org>
  5  *
  6  * SPDX-License-Identifier: GPL-2.0+
  7  *
  8  *  This program is free software; you can redistribute it and/or modify
  9  *  it under the terms of the GNU General Public License as published by
 10  *  the Free Software Foundation; either version 2 of the License, or
 11  *  (at your option) any later version.
 12  *
 13  *  This program is distributed in the hope that it will be useful,
 14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16  *  GNU General Public License for more details.
 17  */
 18 
 19 #include <errno.h>
 20 #include <stdbool.h>
 21 #include <string.h>
 22 #include <inttypes.h>
 23 #include <talloc.h>
 24 
 25 #include "fsm.h"
 26 #include "logging.h"
 27 #include "utils.h"
 28 
 29 /*! \addtogroup fsm
 30  *  @{
 31  *  Finite State Machine abstraction
 32  *
 33  *  This is a generic C-language abstraction for implementing finite
 34  *  state machines within the Osmocom framework.  It is intended to
 35  *  replace existing hand-coded or even only implicitly existing FSMs
 36  *  all over the existing code base.
 37  *
 38  *  An libosmocore FSM is described by its \ref osmo_fsm description,
 39  *  which in turn refers to an array of \ref osmo_fsm_state descriptor,
 40  *  each describing a single state in the FSM.
 41  *
 42  *  The general idea is that all actions performed within one state are
 43  *  located at one position in the code (the state's action function),
 44  *  as opposed to the 'message-centric' view of e.g. the existing
 45  *  state machines of the LAPD(m) core, where there is one message for
 46  *  each possible event (primitive), and the function then needs to
 47  *  concern itself on how to handle that event over all possible states.
 48  *
 49  *  For each state, there is a bit-mask of permitted input events for
 50  *  this state, as well as a bit-mask of permitted new output states to
 51  *  which the state can change.  Furthermore, there is a function
 52  *  pointer implementing the actual handling of the input events
 53  *  occurring whilst in that state.
 54  *
 55  *  Furthermore, each state offers a function pointer that can be
 56  *  executed just before leaving a state, and another one just after
 57  *  entering a state.
 58  *
 59  *  When transitioning into a new state, an optional timer number and
 60  *  time-out can be passed along.  The timer is started just after
 61  *  entering the new state, and will call the \ref osmo_fsm timer_cb
 62  *  function once it expires.  This is intended to be used in telecom
 63  *  state machines where a given timer (identified by a certain number)
 64  *  is started to terminate the fsm or terminate the fsm once expected
 65  *  events are not happening before timeout expiration.
 66  *
 67  *  As there can often be many concurrent FSMs of one given class, we
 68  *  introduce the concept of \ref osmo_fsm_inst, i.e. an FSM instance.
 69  *  The instance keeps the actual state, while the \ref osmo_fsm
 70  *  descriptor contains the static/const descriptor of the FSM's states
 71  *  and possible transitions.
 72  *
 73  *  osmo_fsm are integrated with the libosmocore logging system.  The
 74  *  logging sub-system is determined by the FSM descriptor, as we assume
 75  *  one FSM (let's say one related to a location update procedure) is
 76  *  inevitably always tied to a sub-system.  The logging level however
 77  *  is configurable for each FSM instance, to ensure that e.g. DEBUG
 78  *  logging can be used for the LU procedure of one subscriber, while
 79  *  NOTICE level is used for all other subscribers.
 80  *
 81  *  In order to attach private state to the \ref osmo_fsm_inst, it
 82  *  offers an opaque private pointer.
 83  *
 84  * \file fsm.c */
 85 
 86 LIST_HEAD(osmo_g_fsms);
 87 static bool fsm_log_addr = true;
 88 static bool fsm_log_timeouts = false;
 89 /*! See osmo_fsm_term_safely(). */
 90 static bool fsm_term_safely_enabled = false;
 91 
 92 /*! Internal state for FSM instance termination cascades. */
 93 static __thread struct {
 94         /*! The first FSM instance that invoked osmo_fsm_inst_term() in the current cascade. */
 95         struct osmo_fsm_inst *root_fi;
 96         /*! 2 if a secondary FSM terminates, 3 if a secondary FSM causes a tertiary FSM to terminate, and so on. */
 97         unsigned int depth;
 98         /*! Talloc context to collect all deferred deallocations (FSM instances, and talloc objects if any). */
 99         void *collect_ctx;
100         /*! See osmo_fsm_set_dealloc_ctx() */
101         void *fsm_dealloc_ctx;
102 } fsm_term_safely;
103 
104 /*! Internal call to free an FSM instance, which redirects to the context set by osmo_fsm_set_dealloc_ctx() if any.
105  */
106 static void fsm_free_or_steal(void *talloc_object)
107 {
108         if (fsm_term_safely.fsm_dealloc_ctx)
109                 talloc_steal(fsm_term_safely.fsm_dealloc_ctx, talloc_object);
110         else
111                 talloc_free(talloc_object);
112 }
113 
114 /*! specify if FSM instance addresses should be logged or not
115  *
116  *  By default, the FSM name includes the pointer address of the \ref
117  *  osmo_fsm_inst.  This behavior can be disabled (and re-enabled)
118  *  using this function.
119  *
120  *  \param[in] log_addr Indicate if FSM instance address shall be logged
121  */
122 void osmo_fsm_log_addr(bool log_addr)
123 {
124         fsm_log_addr = log_addr;
125 }
126 
127 /*! Enable or disable logging of timeout values for FSM instance state changes.
128  *
129  * By default, state changes are logged by state name only, omitting the timeout. When passing true, each state change
130  * will also log the T number (or Osmocom-specific X number) and the chosen timeout in seconds.
131  * osmo_fsm_inst_state_chg_keep_timer() will log remaining timeout in millisecond precision.
132  *
133  * The default for this is false to reflect legacy behavior. Since various C tests that verify logging output already
134  * existed prior to this option, keeping timeout logging off makes sure that they continue to pass. Particularly,
135  * osmo_fsm_inst_state_chg_keep_timer() may cause non-deterministic logging of remaining timeout values.
136  *
137  * For any program that does not explicitly require deterministic logging output, i.e. anything besides regression tests
138  * involving FSM instances, it is recommended to call osmo_fsm_log_timeouts(true).
139  *
140  * \param[in] log_timeouts  Pass true to log timeouts on state transitions, false to omit timeouts.
141  */
142 void osmo_fsm_log_timeouts(bool log_timeouts)
143 {
144         fsm_log_timeouts = log_timeouts;
145 }
146 
147 /*! Enable safer way to deallocate cascades of terminating FSM instances.
148  *
149  * Note, using osmo_fsm_set_dealloc_ctx() is a more general solution to this same problem.
150  * Particularly, in a program using osmo_select_main_ctx(), the simplest solution to avoid most use-after-free problems
151  * from FSM instance deallocation is using osmo_fsm_set_dealloc_ctx(OTC_SELECT).
152  *
153  * When enabled, an FSM instance termination detects whether another FSM instance is already terminating, and instead of
154  * deallocating immediately, collects all terminating FSM instances in a talloc context, to be bulk deallocated once all
155  * event handling and termination cascades are done.
156  *
157  * For example, if an FSM's cleanup() sends an event to some "other" FSM, which in turn causes the FSM's parent to
158  * deallocate, then the parent would talloc_free() the child's memory, causing a use-after-free. There are infinite
159  * constellations like this, which all are trivially solved with this feature enabled.
160  *
161  * For illustration, see fsm_dealloc_test.c.
162  *
163  * When enabled, this feature changes the order of logging, which may break legacy unit test expectations, and changes
164  * the order of deallocation to after the parent term event is dispatched.
165  *
166  * \param[in] term_safely  Pass true to switch to safer FSM instance termination behavior.
167  */
168 void osmo_fsm_term_safely(bool term_safely)
169 {
170         fsm_term_safely_enabled = term_safely;
171 }
172 
173 /*! Instead of deallocating FSM instances, move them to the given talloc context.
174  *
175  * It is the caller's responsibility to clear this context to actually free the memory of terminated FSM instances.
176  * Make sure to not talloc_free(ctx) itself before setting a different osmo_fsm_set_dealloc_ctx(). To clear a ctx
177  * without the need to call osmo_fsm_set_dealloc_ctx() again, rather use talloc_free_children(ctx).
178  *
179  * For example, to defer deallocation to the next osmo_select_main_ctx() iteration, set this to OTC_SELECT.
180  *
181  * Deferring deallocation is the simplest solution to avoid most use-after-free problems from FSM instance deallocation.
182  * This is a simpler and more general solution than osmo_fsm_term_safely().
183  *
184  * To disable the feature again, pass NULL as ctx.
185  *
186  * Both osmo_fsm_term_safely() and osmo_fsm_set_dealloc_ctx() can be enabled at the same time, which will result in
187  * first collecting deallocated FSM instances in fsm_term_safely.collect_ctx, and finally reparenting that to the ctx
188  * passed here. However, in practice, it does not really make sense to enable both at the same time.
189  *
190  * \param ctx[in]  Instead of talloc_free()int, talloc_steal() all future deallocated osmo_fsm_inst instances to this
191  *                 ctx. If NULL, go back to talloc_free() as usual.
192  */
193 void osmo_fsm_set_dealloc_ctx(void *ctx)
194 {
195         fsm_term_safely.fsm_dealloc_ctx = ctx;
196 }
197 
198 /*! talloc_free() the given object immediately, or once ongoing FSM terminations are done.
199  *
200  * If an FSM deallocation cascade is ongoing, talloc_steal() the given talloc_object into the talloc context that is
201  * freed once the cascade is done. If no FSM deallocation cascade is ongoing, or if osmo_fsm_term_safely() is disabled,
202  * immediately talloc_free the object.
203  *
204  * This can be useful if some higher order talloc object, which is the talloc parent for FSM instances or their priv
205  * objects, is not itself tied to an FSM instance. This function allows safely freeing it without affecting ongoing FSM
206  * termination cascades.
207  *
208  * Once passed to this function, the talloc_object should be considered as already freed. Only FSM instance pre_term()
209  * and cleanup() functions as well as event handling caused by these may safely assume that it is still valid memory.
210  *
211  * The talloc_object should not have multiple parents.
212  *
213  * (This function may some day move to public API, which might be redundant if we introduce a select-loop volatile
214  * context mechanism to defer deallocation instead.)
215  *
216  * \param[in] talloc_object  Object pointer to free.
217  */
218 static void osmo_fsm_defer_free(void *talloc_object)
219 {
220         if (!fsm_term_safely.depth) {
221                 fsm_free_or_steal(talloc_object);
222                 return;
223         }
224 
225         if (!fsm_term_safely.collect_ctx) {
226                 /* This is actually the first other object / FSM instance besides the root terminating inst. Create the
227                  * ctx to collect this and possibly more objects to free. Avoid talloc parent loops: don't make this ctx
228                  * the child of the root inst or anything like that. */
229                 fsm_term_safely.collect_ctx = talloc_named_const(NULL, 0, "fsm_term_safely.collect_ctx");
230                 OSMO_ASSERT(fsm_term_safely.collect_ctx);
231         }
232         talloc_steal(fsm_term_safely.collect_ctx, talloc_object);
233 }
234 
235 struct osmo_fsm *osmo_fsm_find_by_name(const char *name)
236 {
237         struct osmo_fsm *fsm;
238         list_for_each_entry(fsm, &osmo_g_fsms, list) {
239                 if (!strcmp(name, fsm->name))
240                         return fsm;
241         }
242         return NULL;
243 }
244 
245 struct osmo_fsm_inst *osmo_fsm_inst_find_by_name(const struct osmo_fsm *fsm,
246                                                  const char *name)
247 {
248         struct osmo_fsm_inst *fi;
249 
250         if (!name)
251                 return NULL;
252 
253         list_for_each_entry(fi, &fsm->instances, list) {
254                 if (!fi->name)
255                         continue;
256                 if (!strcmp(name, fi->name))
257                         return fi;
258         }
259         return NULL;
260 }
261 
262 struct osmo_fsm_inst *osmo_fsm_inst_find_by_id(const struct osmo_fsm *fsm,
263                                                 const char *id)
264 {
265         struct osmo_fsm_inst *fi;
266 
267         list_for_each_entry(fi, &fsm->instances, list) {
268                 if (!strcmp(id, fi->id))
269                         return fi;
270         }
271         return NULL;
272 }
273 
274 /*! register a FSM with the core
275  *
276  *  A FSM descriptor needs to be registered with the core before any
277  *  instances can be created for it.
278  *
279  *  \param[in] fsm Descriptor of Finite State Machine to be registered
280  *  \returns 0 on success; negative on error
281  */
282 int osmo_fsm_register(struct osmo_fsm *fsm)
283 {
284         if (!osmo_identifier_valid(fsm->name)) {
285                 LOGP(DLGLOBAL, LOGL_ERROR, "Attempting to register FSM with illegal identifier '%s'\n", fsm->name);
286                 return -EINVAL;
287         }
288         if (osmo_fsm_find_by_name(fsm->name))
289                 return -EEXIST;
290         if (fsm->event_names == NULL)
291                 LOGP(DLGLOBAL, LOGL_ERROR, "FSM '%s' has no event names! Please fix!\n", fsm->name);
292         list_add_tail(&fsm->list, &osmo_g_fsms);
293         INIT_LIST_HEAD(&fsm->instances);
294 
295         return 0;
296 }
297 
298 /*! unregister a FSM from the core
299  *
300  *  Once the FSM descriptor is unregistered, active instances can still
301  *  use it, but no new instances may be created for it.
302  *
303  *  \param[in] fsm Descriptor of Finite State Machine to be removed
304  */
305 void osmo_fsm_unregister(struct osmo_fsm *fsm)
306 {
307         list_del(&fsm->list);
308 }
309 
310 /* small wrapper function around timer expiration (for logging) */
311 static void fsm_tmr_cb(void *data)
312 {
313         struct osmo_fsm_inst *fi = data;
314         struct osmo_fsm *fsm = fi->fsm;
315         int32_t T = fi->T;
316 
317         LOGPFSM(fi, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T));
318 
319         if (fsm->timer_cb) {
320                 int rc = fsm->timer_cb(fi);
321                 if (rc != 1)
322                         /* We don't actually know whether fi exists anymore.
323                          * Make sure to not access it and return right away. */
324                         return;
325                 /* The timer_cb told us to terminate, so we can safely assume
326                  * that fi still exists. */
327                 LOGPFSM(fi, "timer_cb requested termination\n");
328         } else
329                 LOGPFSM(fi, "No timer_cb, automatic termination\n");
330 
331         /* if timer_cb returns 1 or there is no timer_cb */
332         osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, &T);
333 }
334 
335 /*! Change id of the FSM instance
336  * \param[in] fi FSM instance
337  * \param[in] id new ID
338  * \returns 0 if the ID was updated, otherwise -EINVAL
339  */
340 int osmo_fsm_inst_update_id(struct osmo_fsm_inst *fi, const char *id)
341 {
342         if (!id)
343                 return osmo_fsm_inst_update_id_f(fi, NULL);
344         else
345                 return osmo_fsm_inst_update_id_f(fi, "%s", id);
346 }
347 
348 static void update_name(struct osmo_fsm_inst *fi)
349 {
350         if (fi->name)
351                 talloc_free((char*)fi->name);
352 
353         if (!fsm_log_addr) {
354                 if (fi->id)
355                         fi->name = talloc_asprintf(fi, "%s(%s)", fi->fsm->name, fi->id);
356                 else
357                         fi->name = talloc_asprintf(fi, "%s", fi->fsm->name);
358         } else {
359                 if (fi->id)
360                         fi->name = talloc_asprintf(fi, "%s(%s)[%p]", fi->fsm->name, fi->id, fi);
361                 else
362                         fi->name = talloc_asprintf(fi, "%s[%p]", fi->fsm->name, fi);
363         }
364 }
365 
366 /*! Change id of the FSM instance using a string format.
367  * \param[in] fi FSM instance.
368  * \param[in] fmt format string to compose new ID.
369  * \param[in] ... variable argument list for format string.
370  * \returns 0 if the ID was updated, otherwise -EINVAL.
371  */
372 int osmo_fsm_inst_update_id_f(struct osmo_fsm_inst *fi, const char *fmt, ...)
373 {
374         char *id = NULL;
375 
376         if (fmt) {
377                 va_list ap;
378 
379                 va_start(ap, fmt);
380                 id = talloc_vasprintf(fi, fmt, ap);
381                 va_end(ap);
382 
383                 if (!osmo_identifier_valid(id)) {
384                         LOGP(DLGLOBAL, LOGL_ERROR,
385                              "Attempting to set illegal id for FSM instance of type '%s': %s\n",
386                              fi->fsm->name, id);
387                         talloc_free(id);
388                         return -EINVAL;
389                 }
390         }
391 
392         if (fi->id)
393                 talloc_free((char*)fi->id);
394         fi->id = id;
395 
396         update_name(fi);
397         return 0;
398 }
399 
400 /*! Change id of the FSM instance using a string format, and ensuring a valid id.
401  * Replace any characters that are not permitted as FSM identifier with replace_with.
402  * \param[in] fi FSM instance.
403  * \param[in] replace_with Character to use instead of non-permitted FSM id characters.
404  *                         Make sure to choose a legal character, e.g. '-'.
405  * \param[in] fmt format string to compose new ID.
406  * \param[in] ... variable argument list for format string.
407  * \returns 0 if the ID was updated, otherwise -EINVAL.
408  */
409 int osmo_fsm_inst_update_id_f_sanitize(struct osmo_fsm_inst *fi, char replace_with, const char *fmt, ...)
410 {
411         char *id = NULL;
412         va_list ap;
413         int rc;
414 
415         if (!fmt)
416                 return osmo_fsm_inst_update_id(fi, NULL);
417 
418         va_start(ap, fmt);
419         id = talloc_vasprintf(fi, fmt, ap);
420         va_end(ap);
421 
422         osmo_identifier_sanitize_buf(id, NULL, replace_with);
423 
424         rc = osmo_fsm_inst_update_id(fi, id);
425         talloc_free(id);
426         return rc;
427 }
428 
429 /*! allocate a new instance of a specified FSM
430  *  \param[in] fsm Descriptor of the FSM
431  *  \param[in] ctx talloc context from which to allocate memory
432  *  \param[in] priv private data reference store in fsm instance
433  *  \param[in] log_level The log level for events of this FSM
434  *  \param[in] id The name/ID of the FSM instance
435  *  \returns newly-allocated, initialized and registered FSM instance
436  */
437 struct osmo_fsm_inst *osmo_fsm_inst_alloc(struct osmo_fsm *fsm, void *ctx, void *priv,
438                                           int log_level, const char *id)
439 {
440         struct osmo_fsm_inst *fi = talloc_zero(ctx, struct osmo_fsm_inst);
441 
442         fi->fsm = fsm;
443         fi->priv = priv;
444         fi->log_level = log_level;
445         osmo_timer_setup(&fi->timer, fsm_tmr_cb, fi);
446 
447         if (osmo_fsm_inst_update_id(fi, id) < 0) {
448                 fsm_free_or_steal(fi);
449                 return NULL;
450         }
451 
452         INIT_LIST_HEAD(&fi->proc.children);
453         INIT_LIST_HEAD(&fi->proc.child);
454         list_add(&fi->list, &fsm->instances);
455 
456         LOGPFSM(fi, "Allocated\n");
457 
458         return fi;
459 }
460 
461 /*! allocate a new instance of a specified FSM as child of
462  *  other FSM instance
463  *
464  *  This is like \ref osmo_fsm_inst_alloc but using the parent FSM as
465  *  talloc context, and inheriting the log level of the parent.
466  *
467  *  \param[in] fsm Descriptor of the to-be-allocated FSM
468  *  \param[in] parent Parent FSM instance
469  *  \param[in] parent_term_event Event to be sent to parent when terminating
470  *  \returns newly-allocated, initialized and registered FSM instance
471  */
472 struct osmo_fsm_inst *osmo_fsm_inst_alloc_child(struct osmo_fsm *fsm,
473                                                 struct osmo_fsm_inst *parent,
474                                                 uint32_t parent_term_event)
475 {
476         struct osmo_fsm_inst *fi;
477 
478         fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level,
479                                  parent->id);
480         if (!fi) {
481                 /* indicate immediate termination to caller */
482                 osmo_fsm_inst_dispatch(parent, parent_term_event, NULL);
483                 return NULL;
484         }
485 
486         LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent));
487 
488         osmo_fsm_inst_change_parent(fi, parent, parent_term_event);
489 
490         return fi;
491 }
492 
493 /*! unlink child FSM from its parent FSM.
494  *  \param[in] fi Descriptor of the child FSM to unlink.
495  *  \param[in] ctx New talloc context
496  *
497  * Never call this function from the cleanup callback, because at that time
498  * the child FSMs will already be terminated. If unlinking should be performed
499  * on FSM termination, use the grace callback instead. */
500 void osmo_fsm_inst_unlink_parent(struct osmo_fsm_inst *fi, void *ctx)
501 {
502         if (fi->proc.parent) {
503                 talloc_steal(ctx, fi);
504                 fi->proc.parent = NULL;
505                 fi->proc.parent_term_event = 0;
506                 list_del(&fi->proc.child);
507         }
508 }
509 
510 /*! change parent instance of an FSM.
511  *  \param[in] fi Descriptor of the to-be-allocated FSM.
512  *  \param[in] new_parent New parent FSM instance.
513  *  \param[in] new_parent_term_event Event to be sent to parent when terminating.
514  *
515  * Never call this function from the cleanup callback!
516  * (see also osmo_fsm_inst_unlink_parent()).*/
517 void osmo_fsm_inst_change_parent(struct osmo_fsm_inst *fi,
518                                  struct osmo_fsm_inst *new_parent,
519                                  uint32_t new_parent_term_event)
520 {
521         /* Make sure a possibly existing old parent is unlinked first
522          * (new_parent can be NULL) */
523         osmo_fsm_inst_unlink_parent(fi, new_parent);
524 
525         /* Add new parent */
526         if (new_parent) {
527                 fi->proc.parent = new_parent;
528                 fi->proc.parent_term_event = new_parent_term_event;
529                 list_add(&fi->proc.child, &new_parent->proc.children);
530         }
531 }
532 
533 /*! delete a given instance of a FSM
534  *  \param[in] fi FSM instance to be un-registered and deleted
535  */
536 void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
537 {
538         osmo_timer_del(&fi->timer);
539         list_del(&fi->list);
540 
541         if (fsm_term_safely.depth) {
542                 /* Another FSM instance has caused this one to free and is still busy with its termination. Don't free
543                  * yet, until the other FSM instance is done. */
544                 osmo_fsm_defer_free(fi);
545                 /* The root_fi can't go missing really, but to be safe... */
546                 if (fsm_term_safely.root_fi)
547                         LOGPFSM(fi, "Deferring: will deallocate with %s\n", fsm_term_safely.root_fi->name);
548                 else
549                         LOGPFSM(fi, "Deferring deallocation\n");
550 
551                 /* Don't free anything yet. Exit. */
552                 return;
553         }
554 
555         /* fsm_term_safely.depth == 0.
556          * - If fsm_term_safely is enabled, this is the original FSM instance that started terminating first. Free this
557          *   and along with it all other collected terminated FSM instances.
558          * - If fsm_term_safely is disabled, this is just any FSM instance deallocating. */
559 
560         if (fsm_term_safely.collect_ctx) {
561                 /* The fi may be a child of any other FSM instances or objects collected in the collect_ctx. Don't
562                  * deallocate separately to avoid use-after-free errors, put it in there and deallocate all at once. */
563                 LOGPFSM(fi, "Deallocated, including all deferred deallocations\n");
564                 osmo_fsm_defer_free(fi);
565                 fsm_free_or_steal(fsm_term_safely.collect_ctx);
566                 fsm_term_safely.collect_ctx = NULL;
567         } else {
568                 LOGPFSM(fi, "Deallocated\n");
569                 fsm_free_or_steal(fi);
570         }
571         fsm_term_safely.root_fi = NULL;
572 }
573 
574 /*! get human-readable name of FSM event
575  *  \param[in] fsm FSM descriptor of event
576  *  \param[in] event Event integer value
577  *  \returns string rendering of the event
578  */
579 const char *osmo_fsm_event_name(const struct osmo_fsm *fsm, uint32_t event)
580 {
581         static __thread char buf[32];
582         if (!fsm->event_names) {
583                 snprintf(buf, sizeof(buf), "%"PRIu32, event);
584                 return buf;
585         } else
586                 return get_value_string(fsm->event_names, event);
587 }
588 
589 /*! get human-readable name of FSM instance
590  *  \param[in] fi FSM instance
591  *  \returns string rendering of the FSM identity
592  */
593 const char *osmo_fsm_inst_name(const struct osmo_fsm_inst *fi)
594 {
595         if (!fi)
596                 return "NULL";
597 
598         if (fi->name)
599                 return fi->name;
600         else
601                 return fi->fsm->name;
602 }
603 
604 /*! get human-readable name of FSM state
605  *  \param[in] fsm FSM descriptor
606  *  \param[in] state FSM state number
607  *  \returns string rendering of the FSM state
608  */
609 const char *osmo_fsm_state_name(const struct osmo_fsm *fsm, uint32_t state)
610 {
611         static __thread char buf[32];
612         if (state >= fsm->num_states) {
613                 snprintf(buf, sizeof(buf), "unknown %"PRIu32, state);
614                 return buf;
615         } else
616                 return fsm->states[state].name;
617 }
618 
619 static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
620                      bool keep_timer, unsigned long timeout_ms, int T,
621                      const char *file, int line)
622 {
623         struct osmo_fsm *fsm = fi->fsm;
624         uint32_t old_state = fi->state;
625         const struct osmo_fsm_state *st = &fsm->states[fi->state];
626         struct timeval remaining;
627 
628         if (fi->proc.terminating) {
629                 LOGPFSMSRC(fi, file, line,
630                            "FSM instance already terminating, not changing state to %s\n",
631                            osmo_fsm_state_name(fsm, new_state));
632                 return -EINVAL;
633         }
634 
635         /* validate if new_state is a valid state */
636         if (!(st->out_state_mask & (1 << new_state))) {
637                 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
638                             "transition to state %s not permitted!\n",
639                             osmo_fsm_state_name(fsm, new_state));
640                 return -EPERM;
641         }
642 
643         if (!keep_timer) {
644                 /* delete the old timer */
645                 osmo_timer_del(&fi->timer);
646         }
647 
648         if (st->onleave)
649                 st->onleave(fi, new_state);
650 
651         if (fsm_log_timeouts) {
652                 char trailer[64];
653                 trailer[0] = '\0';
654                 if (keep_timer && osmo_timer_pending(&fi->timer)) {
655                         /* This should always give us a timeout, but just in case the return value indicates error, omit
656                          * logging the remaining time. */
657                         if (osmo_timer_remaining(&fi->timer, NULL, &remaining))
658                                 snprintf(trailer, sizeof(trailer), "(keeping " OSMO_T_FMT ")",
659                                          OSMO_T_FMT_ARGS(fi->T));
660                         else
661                                 snprintf(trailer, sizeof(trailer), "(keeping " OSMO_T_FMT
662                                           ", %ld.%03lds remaining)", OSMO_T_FMT_ARGS(fi->T),
663                                           (long) remaining.tv_sec, (long) remaining.tv_usec / 1000);
664                 } else if (timeout_ms) {
665                         if (timeout_ms % 1000 == 0)
666                                 /* keep log output legacy compatible to avoid autotest failures */
667                                 snprintf(trailer, sizeof(trailer), "(" OSMO_T_FMT ", %lus)",
668                                            OSMO_T_FMT_ARGS(T), timeout_ms/1000);
669                         else
670                                 snprintf(trailer, sizeof(trailer), "(" OSMO_T_FMT ", %lums)",
671                                            OSMO_T_FMT_ARGS(T), timeout_ms);
672                 } else
673                         snprintf(trailer, sizeof(trailer), "(no timeout)");
674 
675                 LOGPFSMSRC(fi, file, line, "State change to %s %s\n",
676                            osmo_fsm_state_name(fsm, new_state), trailer);
677         } else {
678                 LOGPFSMSRC(fi, file, line, "state_chg to %s\n",
679                            osmo_fsm_state_name(fsm, new_state));
680         }
681 
682         fi->state = new_state;
683         st = &fsm->states[new_state];
684 
685         if (!keep_timer
686             || (keep_timer && !osmo_timer_pending(&fi->timer))) {
687                 fi->T = T;
688                 if (timeout_ms) {
689                         osmo_timer_schedule(&fi->timer,
690                               /* seconds */ (timeout_ms / 1000),
691                          /* microseconds */ (timeout_ms % 1000) * 1000);
692                 }
693         }
694 
695         /* Call 'onenter' last, user might terminate FSM from there */
696         if (st->onenter)
697                 st->onenter(fi, old_state);
698 
699         return 0;
700 }
701 
702 /*! perform a state change of the given FSM instance
703  *
704  *  Best invoke via the osmo_fsm_inst_state_chg() macro which logs the source
705  *  file where the state change was effected. Alternatively, you may pass \a
706  *  file as NULL to use the normal file/line indication instead.
707  *
708  *  All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
709  *  function.  It verifies that the existing state actually permits a
710  *  transition to new_state.
711  *
712  *  If timeout_secs is 0, stay in the new state indefinitely, without a timeout
713  *  (stop the FSM instance's timer if it was runnning).
714  *
715  *  If timeout_secs > 0, start or reset the FSM instance's timer with this
716  *  timeout. On expiry, invoke the FSM instance's timer_cb -- if no timer_cb is
717  *  set, an expired timer immediately terminates the FSM instance with
718  *  OSMO_FSM_TERM_TIMEOUT.
719  *
720  *  The value of T is stored in fi->T and is then available for query in
721  *  timer_cb. If passing timeout_secs == 0, it is recommended to also pass T ==
722  *  0, so that fi->T is reset to 0 when no timeout is invoked.
723  *
724  *  Positive values for T are considered to be 3GPP spec compliant and appear in
725  *  logging and VTY as "T1234", while negative values are considered to be
726  *  Osmocom specific timers, represented in logging and VTY as "X1234".
727  *
728  *  See also osmo_tdef_fsm_inst_state_chg() from the osmo_tdef API, which
729  *  provides a unified way to configure and apply GSM style Tnnnn timers to FSM
730  *  state transitions.
731  *
732  *  \param[in] fi FSM instance whose state is to change
733  *  \param[in] new_state The new state into which we should change
734  *  \param[in] timeout_secs Timeout in seconds (if !=0), maximum-clamped to 2147483647 seconds.
735  *  \param[in] T Timer number, where positive numbers are considered to be 3GPP spec compliant timer numbers and are
736  *               logged as "T1234", while negative numbers are considered Osmocom specific timer numbers logged as
737  *               "X1234".
738  *  \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
739  *  \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
740  *  \returns 0 on success; negative on error
741  */
742 int _osmo_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
743                              unsigned long timeout_secs, int T,
744                              const char *file, int line)
745 {
746         return state_chg(fi, new_state, false, timeout_secs*1000, T, file, line);
747 }
748 int _osmo_fsm_inst_state_chg_ms(struct osmo_fsm_inst *fi, uint32_t new_state,
749                                 unsigned long timeout_ms, int T,
750                                 const char *file, int line)
751 {
752         return state_chg(fi, new_state, false, timeout_ms, T, file, line);
753 }
754 
755 /*! perform a state change while keeping the current timer running.
756  *
757  *  This is useful to keep a timeout across several states (without having to round the
758  *  remaining time to seconds).
759  *
760  *  Best invoke via the osmo_fsm_inst_state_chg_keep_timer() macro which logs the source
761  *  file where the state change was effected. Alternatively, you may pass \a
762  *  file as NULL to use the normal file/line indication instead.
763  *
764  *  All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
765  *  function.  It verifies that the existing state actually permits a
766  *  transition to new_state.
767  *
768  *  \param[in] fi FSM instance whose state is to change
769  *  \param[in] new_state The new state into which we should change
770  *  \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
771  *  \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
772  *  \returns 0 on success; negative on error
773  */
774 int _osmo_fsm_inst_state_chg_keep_timer(struct osmo_fsm_inst *fi, uint32_t new_state,
775                                         const char *file, int line)
776 {
777         return state_chg(fi, new_state, true, 0, 0, file, line);
778 }
779 
780 /*! perform a state change while keeping the current timer if running, or starting a timer otherwise.
781  *
782  *  This is useful to keep a timeout across several states, but to make sure that some timeout is actually running.
783  *
784  *  Best invoke via the osmo_fsm_inst_state_chg_keep_or_start_timer() macro which logs the source file where the state
785  *  change was effected. Alternatively, you may pass file as NULL to use the normal file/line indication instead.
786  *
787  *  All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
788  *  function.  It verifies that the existing state actually permits a
789  *  transition to new_state.
790  *
791  *  \param[in] fi FSM instance whose state is to change
792  *  \param[in] new_state The new state into which we should change
793  *  \param[in] timeout_secs If no timer is running yet, set this timeout in seconds (if !=0), maximum-clamped to
794  *                          2147483647 seconds.
795  *  \param[in] T Timer number, where positive numbers are considered to be 3GPP spec compliant timer numbers and are
796  *               logged as "T1234", while negative numbers are considered Osmocom specific timer numbers logged as
797  *               "X1234".
798  *  \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
799  *  \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
800  *  \returns 0 on success; negative on error
801  */
802 int _osmo_fsm_inst_state_chg_keep_or_start_timer(struct osmo_fsm_inst *fi, uint32_t new_state,
803                                                  unsigned long timeout_secs, int T,
804                                                  const char *file, int line)
805 {
806         return state_chg(fi, new_state, true, timeout_secs*1000, T, file, line);
807 }
808 int _osmo_fsm_inst_state_chg_keep_or_start_timer_ms(struct osmo_fsm_inst *fi, uint32_t new_state,
809                                                     unsigned long timeout_ms, int T,
810                                                     const char *file, int line)
811 {
812         return state_chg(fi, new_state, true, timeout_ms, T, file, line);
813 }
814 
815 
816 /*! dispatch an event to an osmocom finite state machine instance
817  *
818  *  Best invoke via the osmo_fsm_inst_dispatch() macro which logs the source
819  *  file where the event was effected. Alternatively, you may pass \a file as
820  *  NULL to use the normal file/line indication instead.
821  *
822  *  Any incoming events to \ref osmo_fsm instances must be dispatched to
823  *  them via this function.  It verifies, whether the event is permitted
824  *  based on the current state of the FSM.  If not, -1 is returned.
825  *
826  *  \param[in] fi FSM instance
827  *  \param[in] event Event to send to FSM instance
828  *  \param[in] data Data to pass along with the event
829  *  \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
830  *  \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
831  *  \returns 0 in case of success; negative on error
832  */
833 int _osmo_fsm_inst_dispatch(struct osmo_fsm_inst *fi, uint32_t event, void *data,
834                             const char *file, int line)
835 {
836         struct osmo_fsm *fsm;
837         const struct osmo_fsm_state *fs;
838 
839         if (!fi) {
840                 LOGPSRC(DLGLOBAL, LOGL_ERROR, file, line,
841                         "Trying to dispatch event %"PRIu32" to non-existent"
842                         " FSM instance!\n", event);
843                 osmo_log_backtrace(DLGLOBAL, LOGL_ERROR);
844                 return -ENODEV;
845         }
846 
847         fsm = fi->fsm;
848 
849         if (fi->proc.terminating) {
850                 LOGPFSMSRC(fi, file, line,
851                            "FSM instance already terminating, not dispatching event %s\n",
852                            osmo_fsm_event_name(fsm, event));
853                 return -EINVAL;
854         }
855 
856         OSMO_ASSERT(fi->state < fsm->num_states);
857         fs = &fi->fsm->states[fi->state];
858 
859         LOGPFSMSRC(fi, file, line,
860                    "Received Event %s\n", osmo_fsm_event_name(fsm, event));
861 
862         if (((1 << event) & fsm->allstate_event_mask) && fsm->allstate_action) {
863                 fsm->allstate_action(fi, event, data);
864                 return 0;
865         }
866 
867         if (!((1 << event) & fs->in_event_mask)) {
868                 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
869                             "Event %s not permitted\n",
870                             osmo_fsm_event_name(fsm, event));
871                 return -1;
872         }
873 
874         if (fs->action)
875                 fs->action(fi, event, data);
876 
877         return 0;
878 }
879 
880 /*! Terminate FSM instance with given cause
881  *
882  *  This safely terminates the given FSM instance by first iterating
883  *  over all children and sending them a termination event.  Next, it
884  *  calls the FSM descriptors cleanup function (if any), followed by
885  *  releasing any memory associated with the FSM instance.
886  *
887  *  Finally, the parent FSM instance (if any) is notified using the
888  *  parent termination event configured at time of FSM instance start.
889  *
890  *  \param[in] fi FSM instance to be terminated
891  *  \param[in] cause Cause / reason for termination
892  *  \param[in] data Opaque event data to be passed with the parent term event
893  *  \param[in] file Calling source file (from osmo_fsm_inst_term macro)
894  *  \param[in] line Calling source line (from osmo_fsm_inst_term macro)
895  */
896 void _osmo_fsm_inst_term(struct osmo_fsm_inst *fi,
897                          enum osmo_fsm_term_cause cause, void *data,
898                          const char *file, int line)
899 {
900         struct osmo_fsm_inst *parent;
901         uint32_t parent_term_event = fi->proc.parent_term_event;
902 
903         if (fi->proc.terminating) {
904                 LOGPFSMSRC(fi, file, line, "Ignoring trigger to terminate: already terminating\n");
905                 return;
906         }
907         fi->proc.terminating = true;
908 
909         /* Start termination cascade handling only if the feature is enabled. Also check the current depth: though
910          * unlikely, theoretically the fsm_term_safely_enabled flag could be toggled in the middle of a cascaded
911          * termination, so make sure to continue if it already started. */
912         if (fsm_term_safely_enabled || fsm_term_safely.depth) {
913                 fsm_term_safely.depth++;
914                 /* root_fi is just for logging, so no need to be extra careful about it. */
915                 if (!fsm_term_safely.root_fi)
916                         fsm_term_safely.root_fi = fi;
917         }
918 
919         if (fsm_term_safely.depth > 1) {
920                 /* fsm_term_safely is enabled and this is a secondary FSM instance terminated, caused by the root_fi. */
921                 LOGPFSMSRC(fi, file, line, "Terminating in cascade, depth %d (cause = %s, caused by: %s)\n",
922                            fsm_term_safely.depth, osmo_fsm_term_cause_name(cause),
923                            fsm_term_safely.root_fi ? fsm_term_safely.root_fi->name : "unknown");
924                 /* The root_fi can't go missing really, but to be safe, log "unknown" in that case. */
925         } else {
926                 /* fsm_term_safely is disabled, or this is the root_fi. */
927                 LOGPFSMSRC(fi, file, line, "Terminating (cause = %s)\n", osmo_fsm_term_cause_name(cause));
928         }
929 
930         /* graceful exit (optional) */
931         if (fi->fsm->pre_term)
932                 fi->fsm->pre_term(fi, cause);
933 
934         _osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL,
935                                      file, line);
936 
937         /* delete ourselves from the parent */
938         parent = fi->proc.parent;
939         if (parent) {
940                 LOGPFSMSRC(fi, file, line, "Removing from parent %s\n",
941                            osmo_fsm_inst_name(parent));
942                 list_del(&fi->proc.child);
943         }
944 
945         /* call destructor / clean-up function */
946         if (fi->fsm->cleanup)
947                 fi->fsm->cleanup(fi, cause);
948 
949         /* Fetch parent again in case it has changed. */
950         parent = fi->proc.parent;
951 
952         /* Legacy behavior if fsm_term_safely is disabled: free before dispatching parent event. (If fsm_term_safely is
953          * enabled, depth will *always* be > 0 here.) Pivot on depth instead of the enabled flag in case the enabled
954          * flag is toggled in the middle of an FSM term. */
955         if (!fsm_term_safely.depth) {
956                 LOGPFSMSRC(fi, file, line, "Freeing instance\n");
957                 osmo_fsm_inst_free(fi);
958         }
959 
960         /* indicate our termination to the parent */
961         if (parent && cause != OSMO_FSM_TERM_PARENT)
962                 _osmo_fsm_inst_dispatch(parent, parent_term_event, data,
963                                         file, line);
964 
965         /* Newer, safe deallocation: free only after the parent_term_event was dispatched, to catch all termination
966          * cascades, and free all FSM instances at once. (If fsm_term_safely is enabled, depth will *always* be > 0
967          * here.) osmo_fsm_inst_free() will do the defer magic depending on the fsm_term_safely.depth. */
968         if (fsm_term_safely.depth) {
969                 fsm_term_safely.depth--;
970                 osmo_fsm_inst_free(fi);
971         }
972 }
973 
974 /*! Terminate all child FSM instances of an FSM instance.
975  *
976  *  Iterate over all children and send them a termination event, with the given
977  *  cause. Pass OSMO_FSM_TERM_PARENT to avoid dispatching events from the
978  *  terminated child FSMs.
979  *
980  *  \param[in] fi FSM instance that should be cleared of child FSMs
981  *  \param[in] cause Cause / reason for termination (OSMO_FSM_TERM_PARENT)
982  *  \param[in] data Opaque event data to be passed with the parent term events
983  *  \param[in] file Calling source file (from osmo_fsm_inst_term_children macro)
984  *  \param[in] line Calling source line (from osmo_fsm_inst_term_children macro)
985  */
986 void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
987                                   enum osmo_fsm_term_cause cause,
988                                   void *data,
989                                   const char *file, int line)
990 {
991         struct osmo_fsm_inst *first_child, *last_seen_first_child;
992 
993         /* iterate over all children, starting from the beginning every time:
994          * terminating an FSM may emit events that cause other FSMs to also
995          * terminate and remove themselves from this list. */
996         last_seen_first_child = NULL;
997         while (!list_empty(&fi->proc.children)) {
998                 first_child = list_entry(fi->proc.children.next,
999                                           typeof(*first_child),
1000                                           proc.child);
1001 
1002                 /* paranoia: do not loop forever */
1003                 if (first_child == last_seen_first_child) {
1004                         LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
1005                                     "Internal error while terminating child"
1006                                     " FSMs: a child FSM is stuck\n");
1007                         break;
1008                 }
1009                 last_seen_first_child = first_child;
1010 
1011                 /* terminate child */
1012                 _osmo_fsm_inst_term(first_child, cause, data,
1013                                     file, line);
1014         }
1015 }
1016 
1017 /*! Broadcast an event to all the FSMs children.
1018  *
1019  *  Iterate over all children and send them the specified event.
1020  *
1021  *  \param[in] fi FSM instance of the parent
1022  *  \param[in] event Event to send to children of FSM instance
1023  *  \param[in] data Data to pass along with the event
1024  *  \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
1025  *  \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
1026  */
1027 void _osmo_fsm_inst_broadcast_children(struct osmo_fsm_inst *fi,
1028                                         uint32_t event, void *data,
1029                                         const char *file, int line)
1030 {
1031         struct osmo_fsm_inst *child, *tmp;
1032         list_for_each_entry_safe(child, tmp, &fi->proc.children, proc.child) {
1033                 _osmo_fsm_inst_dispatch(child, event, data, file, line);
1034         }
1035 }
1036 
1037 const struct value_string osmo_fsm_term_cause_names[] = {
1038         OSMO_VALUE_STRING(OSMO_FSM_TERM_PARENT),
1039         OSMO_VALUE_STRING(OSMO_FSM_TERM_REQUEST),
1040         OSMO_VALUE_STRING(OSMO_FSM_TERM_REGULAR),
1041         OSMO_VALUE_STRING(OSMO_FSM_TERM_ERROR),
1042         OSMO_VALUE_STRING(OSMO_FSM_TERM_TIMEOUT),
1043         { 0, NULL }
1044 };
1045 
1046 /*! @} */
1047 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt