Asterisk - The Open Source Telephony Project GIT-master-67613d1
astobj2_container.c
Go to the documentation of this file.
1/* astobj2 - replacement containers for asterisk data structures.
2 *
3 * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy
4 *
5 * See http://www.asterisk.org for more information about
6 * the Asterisk project. Please do not directly contact
7 * any of the maintainers of this project for assistance;
8 * the project provides a web site, mailing lists and IRC
9 * channels for your use.
10 *
11 * This program is free software, distributed under the terms of
12 * the GNU General Public License Version 2. See the LICENSE file
13 * at the top of the source tree.
14 */
15
16/*! \file
17 *
18 * \brief Functions implementing astobj2 objects.
19 *
20 * \author Richard Mudgett <rmudgett@digium.com>
21 */
22
23#include "asterisk.h"
24
25#include "asterisk/_private.h"
26#include "asterisk/astobj2.h"
27#include "astobj2_private.h"
29#include "asterisk/cli.h"
30
31/*!
32 * return the number of elements in the container
33 */
35{
36 return ast_atomic_fetchadd_int(&c->elements, 0);
37}
38
40 const char *tag, const char *file, int line, const char *func)
41{
42 struct ao2_container *container = node->my_container;
43
44 if (container == NULL && (flags & AO2_UNLINK_NODE_DEC_COUNT)) {
45 return 0;
46 }
47
49 && !(flags & AO2_UNLINK_NODE_NOUNREF_OBJECT)) {
50 __ao2_ref(node->obj, -1, tag ?: "Remove obj from container", file, line, func);
51 }
52
53 node->obj = NULL;
54
55 if (flags & AO2_UNLINK_NODE_DEC_COUNT) {
57#if defined(AO2_DEBUG)
58 {
59 int empty = container->nodes - container->elements;
60
61 if (container->max_empty_nodes < empty) {
62 container->max_empty_nodes = empty;
63 }
64 if (container->v_table->unlink_stat) {
65 container->v_table->unlink_stat(container, node);
66 }
67 }
68#endif /* defined(AO2_DEBUG) */
69 }
70
71 if (flags & AO2_UNLINK_NODE_UNREF_NODE) {
72 /* Remove node from container */
73 ao2_ref(node, -1);
74 }
75
76 return 1;
77}
78
79/*!
80 * \internal
81 * \brief Link an object into this container. (internal)
82 *
83 * \param self Container to operate upon.
84 * \param obj_new Object to insert into the container.
85 * \param flags search_flags to control linking the object. (OBJ_NOLOCK)
86 * \param tag used for debugging.
87 * \param file Debug file name invoked from
88 * \param line Debug line invoked from
89 * \param func Debug function name invoked from
90 *
91 * \retval 0 on errors.
92 * \retval 1 on success.
93 */
94int __ao2_link(struct ao2_container *self, void *obj_new, int flags,
95 const char *tag, const char *file, int line, const char *func)
96{
97 int res;
98 enum ao2_lock_req orig_lock;
100
101 if (!__is_ao2_object(obj_new, file, line, func)
102 || !__is_ao2_object(self, file, line, func)) {
103 return 0;
104 }
105
106 if (!self->v_table || !self->v_table->new_node || !self->v_table->insert) {
107 /* Sanity checks. */
108 __ast_assert_failed(0, "invalid container v_table", file, line, func);
109 return 0;
110 }
111
112 if (flags & OBJ_NOLOCK) {
113 orig_lock = __adjust_lock(self, AO2_LOCK_REQ_WRLOCK, 1);
114 } else {
115 ao2_wrlock(self);
116 orig_lock = AO2_LOCK_REQ_MUTEX;
117 }
118
119 res = 0;
120 node = self->v_table->new_node(self, obj_new, tag, file, line, func);
121 if (node) {
122#if defined(AO2_DEBUG)
123 if (ao2_container_check(self, OBJ_NOLOCK)) {
124 ast_log(LOG_ERROR, "Container integrity failed before insert.\n");
125 }
126#endif /* defined(AO2_DEBUG) */
127
128 /* Insert the new node. */
129 switch (self->v_table->insert(self, node)) {
131 node->is_linked = 1;
133#if defined(AO2_DEBUG)
134 AO2_DEVMODE_STAT(++self->nodes);
135 if (self->v_table->link_stat) {
136 self->v_table->link_stat(self, node);
137 }
138#endif /* defined(AO2_DEBUG) */
139 /* Fall through */
141#if defined(AO2_DEBUG)
142 if (ao2_container_check(self, OBJ_NOLOCK)) {
143 ast_log(LOG_ERROR, "Container integrity failed after insert or replace.\n");
144 }
145#endif /* defined(AO2_DEBUG) */
146 res = 1;
147 break;
149 ao2_ref(node, -1);
150 break;
151 }
152 }
153
154 if (flags & OBJ_NOLOCK) {
155 __adjust_lock(self, orig_lock, 0);
156 } else {
157 ao2_unlock(self);
158 }
159
160 return res;
161}
162
163/*!
164 * \brief another convenience function is a callback that matches on address
165 */
166int ao2_match_by_addr(void *user_data, void *arg, int flags)
167{
168 return (user_data == arg) ? (CMP_MATCH | CMP_STOP) : 0;
169}
170
171/*
172 * Unlink an object from the container
173 * and destroy the associated * bucket_entry structure.
174 */
175void *__ao2_unlink(struct ao2_container *c, void *user_data, int flags,
176 const char *tag, const char *file, int line, const char *func)
177{
178 if (!__is_ao2_object(user_data, file, line, func)) {
179 /* Sanity checks. */
180 return NULL;
181 }
182
183 flags &= ~OBJ_SEARCH_MASK;
185 __ao2_callback(c, flags, ao2_match_by_addr, user_data, tag, file, line, func);
186
187 return NULL;
188}
189
190/*!
191 * \brief special callback that matches all
192 */
193static int cb_true(void *user_data, void *arg, int flags)
194{
195 return CMP_MATCH;
196}
197
198/*!
199 * \brief similar to cb_true, but is an ao2_callback_data_fn instead
200 */
201static int cb_true_data(void *user_data, void *arg, void *data, int flags)
202{
203 return CMP_MATCH;
204}
205
206/*!
207 * \internal
208 * \brief Traverse the container. (internal)
209 *
210 * \param self Container to operate upon.
211 * \param flags search_flags to control traversing the container
212 * \param cb_fn Comparison callback function.
213 * \param arg Comparison callback arg parameter.
214 * \param data Data comparison callback data parameter.
215 * \param type Type of comparison callback cb_fn.
216 * \param tag used for debugging.
217 * \param file Debug file name invoked from
218 * \param line Debug line invoked from
219 * \param func Debug function name invoked from
220 *
221 * \retval NULL on failure or no matching object found.
222 *
223 * \retval object found if OBJ_MULTIPLE is not set in the flags
224 * parameter.
225 *
226 * \retval ao2_iterator pointer if OBJ_MULTIPLE is set in the
227 * flags parameter. The iterator must be destroyed with
228 * ao2_iterator_destroy() when the caller no longer needs it.
229 */
230static void *internal_ao2_traverse(struct ao2_container *self, enum search_flags flags,
231 void *cb_fn, void *arg, void *data, enum ao2_callback_type type,
232 const char *tag, const char *file, int line, const char *func)
233{
234 void *ret;
235 ao2_callback_fn *cb_default = NULL;
236 ao2_callback_data_fn *cb_withdata = NULL;
237 struct ao2_container_node *node;
238 void *traversal_state;
239
240 enum ao2_lock_req orig_lock;
241 struct ao2_container *multi_container = NULL;
242 struct ao2_iterator *multi_iterator = NULL;
243
244 if (!__is_ao2_object(self, file, line, func)) {
245 return NULL;
246 }
247
248 if (!self->v_table
249 || !self->v_table->traverse_first || !self->v_table->traverse_next) {
250 /* Sanity checks. */
251 __ast_assert_failed(0, "invalid container v_table", file, line, func);
252 return NULL;
253 }
254
255 /*
256 * This logic is used so we can support OBJ_MULTIPLE with OBJ_NODATA
257 * turned off. This if statement checks for the special condition
258 * where multiple items may need to be returned.
259 */
260 if ((flags & (OBJ_MULTIPLE | OBJ_NODATA)) == OBJ_MULTIPLE) {
261 /* we need to return an ao2_iterator with the results,
262 * as there could be more than one. the iterator will
263 * hold the only reference to a container that has all the
264 * matching objects linked into it, so when the iterator
265 * is destroyed, the container will be automatically
266 * destroyed as well.
267 */
269 NULL, "OBJ_MULTIPLE return container creation");
270 if (!multi_container) {
271 return NULL;
272 }
273 if (!(multi_iterator = ast_calloc(1, sizeof(*multi_iterator)))) {
274 ao2_t_ref(multi_container, -1, "OBJ_MULTIPLE interator creation failed.");
275 return NULL;
276 }
277 }
278
279 if (!cb_fn) {
280 /* Match everything if no callback match function provided. */
282 cb_withdata = cb_true_data;
283 } else {
284 cb_default = cb_true;
285 }
286 } else {
287 /*
288 * We do this here to avoid the per object casting penalty (even
289 * though that is probably optimized away anyway).
290 */
292 cb_withdata = cb_fn;
293 } else {
294 cb_default = cb_fn;
295 }
296 }
297
298 /* avoid modifications to the content */
299 if (flags & OBJ_NOLOCK) {
300 if (flags & OBJ_UNLINK) {
301 orig_lock = __adjust_lock(self, AO2_LOCK_REQ_WRLOCK, 1);
302 } else {
303 orig_lock = __adjust_lock(self, AO2_LOCK_REQ_RDLOCK, 1);
304 }
305 } else {
306 orig_lock = AO2_LOCK_REQ_MUTEX;
307 if (flags & OBJ_UNLINK) {
308 ao2_wrlock(self);
309 } else {
310 ao2_rdlock(self);
311 }
312 }
313
314 /* Create a buffer for the traversal state. */
315 traversal_state = alloca(AO2_TRAVERSAL_STATE_SIZE);
316
317 ret = NULL;
318 for (node = self->v_table->traverse_first(self, flags, arg, traversal_state);
319 node;
320 node = self->v_table->traverse_next(self, traversal_state, node)) {
321 int match;
322
323 /* Visit the current node. */
326 match &= cb_withdata(node->obj, arg, data, flags);
327 } else {
328 match &= cb_default(node->obj, arg, flags);
329 }
330 if (match == 0) {
331 /* no match, no stop, continue */
332 continue;
333 }
334 if (match == CMP_STOP) {
335 /* no match but stop, we are done */
336 break;
337 }
338
339 /*
340 * CMP_MATCH is set here
341 *
342 * we found the object, performing operations according to flags
343 */
344 if (node->obj) {
345 /* The object is still in the container. */
346 if (!(flags & OBJ_NODATA)) {
347 /*
348 * We are returning the object, record the value. It is
349 * important to handle this case before the unlink.
350 */
351 if (multi_container) {
352 /*
353 * Link the object into the container that will hold the
354 * results.
355 */
356 __ao2_link(multi_container, node->obj, flags, tag, file, line, func);
357 } else {
358 ret = node->obj;
359 /* Returning a single object. */
360 if (!(flags & OBJ_UNLINK)) {
361 /*
362 * Bump the ref count since we are not going to unlink and
363 * transfer the container's object ref to the returned object.
364 */
365 __ao2_ref(ret, 1, tag ?: "Traversal found object", file, line, func);
366 }
367 }
368 }
369
370 if (flags & OBJ_UNLINK) {
372 if (multi_container || (flags & OBJ_NODATA)) {
374 }
375 __container_unlink_node_debug(node, ulflag, tag, file, line, func);
376 }
377 }
378
379 if ((match & CMP_STOP) || !(flags & OBJ_MULTIPLE)) {
380 /* We found our only (or last) match, so we are done */
381 break;
382 }
383 }
384 if (self->v_table->traverse_cleanup) {
385 self->v_table->traverse_cleanup(traversal_state);
386 }
387 if (node) {
388 /* Unref the node from self->v_table->traverse_first/traverse_next() */
389 ao2_ref(node, -1);
390 }
391
392 if (flags & OBJ_NOLOCK) {
393 __adjust_lock(self, orig_lock, 0);
394 } else {
395 ao2_unlock(self);
396 }
397
398 /* if multi_container was created, we are returning multiple objects */
399 if (multi_container) {
400 *multi_iterator = ao2_iterator_init(multi_container,
402 ao2_t_ref(multi_container, -1,
403 "OBJ_MULTIPLE for multiple objects traversal complete.");
404 return multi_iterator;
405 } else {
406 return ret;
407 }
408}
409
411 ao2_callback_fn *cb_fn, void *arg, const char *tag, const char *file, int line,
412 const char *func)
413{
414 return internal_ao2_traverse(c, flags, cb_fn, arg, NULL, AO2_CALLBACK_DEFAULT, tag, file, line, func);
415}
416
418 ao2_callback_data_fn *cb_fn, void *arg, void *data, const char *tag, const char *file,
419 int line, const char *func)
420{
421 return internal_ao2_traverse(c, flags, cb_fn, arg, data, AO2_CALLBACK_WITH_DATA, tag, file, line, func);
422}
423
424/*!
425 * the find function just invokes the default callback with some reasonable flags.
426 */
427void *__ao2_find(struct ao2_container *c, const void *arg, enum search_flags flags,
428 const char *tag, const char *file, int line, const char *func)
429{
430 void *arged = (void *) arg;/* Done to avoid compiler const warning */
431
432 if (!c) {
433 /* Sanity checks. */
434 ast_assert(0);
435 return NULL;
436 }
437 return __ao2_callback(c, flags, c->cmp_fn, arged, tag, file, line, func);
438}
439
440void *__ao2_weakproxy_find(struct ao2_container *c, const void *arg, enum search_flags flags,
441 const char *tag, const char *file, int line, const char *func)
442{
443 void *proxy;
444 void *obj = NULL;
445 enum ao2_lock_req orig_lock;
446
447 ast_assert(!!c);
450
451 if (flags & OBJ_NOLOCK) {
452 orig_lock = __adjust_lock(c, AO2_LOCK_REQ_RDLOCK, 1);
453 } else {
454 orig_lock = AO2_LOCK_REQ_RDLOCK;
455 ao2_rdlock(c);
456 }
457
458 while ((proxy = ao2_find(c, arg, flags | OBJ_NOLOCK))) {
459 obj = __ao2_weakproxy_get_object(proxy, 0, tag ?: __PRETTY_FUNCTION__, file, line, func);
460
461 if (obj) {
462 ao2_ref(proxy, -1);
463 break;
464 }
465
466 /* Upgrade to a write lock */
469 ao2_ref(proxy, -1);
470 }
471
472 if (flags & OBJ_NOLOCK) {
473 /* We'll keep any upgraded lock */
474 __adjust_lock(c, orig_lock, 1);
475 } else {
476 ao2_unlock(c);
477 }
478
479 return obj;
480}
481
482/*!
483 * initialize an iterator so we start from the first object
484 */
486{
487 struct ao2_iterator a = {
488 .c = c,
489 .flags = flags
490 };
491
492 ao2_t_ref(c, +1, "Init iterator with container.");
493
494 return a;
495}
496
498{
499 if (!is_ao2_object(iter->c)) {
500 /* Sanity check. */
501 return;
502 }
503
504 /* Release the last container node reference if we have one. */
505 if (iter->last_node) {
506 enum ao2_lock_req orig_lock;
507
508 /*
509 * Do a read lock in case the container node unref does not
510 * destroy the node. If the container node is destroyed then
511 * the lock will be upgraded to a write lock.
512 */
513 if (iter->flags & AO2_ITERATOR_DONTLOCK) {
514 orig_lock = __adjust_lock(iter->c, AO2_LOCK_REQ_RDLOCK, 1);
515 } else {
516 orig_lock = AO2_LOCK_REQ_MUTEX;
517 ao2_rdlock(iter->c);
518 }
519
520 ao2_ref(iter->last_node, -1);
521 iter->last_node = NULL;
522
523 if (iter->flags & AO2_ITERATOR_DONTLOCK) {
524 __adjust_lock(iter->c, orig_lock, 0);
525 } else {
526 ao2_unlock(iter->c);
527 }
528 }
529
530 /* The iteration is no longer complete. */
531 iter->complete = 0;
532}
533
535{
536 /* Release any last container node reference. */
538
539 /* Release the iterated container reference. */
540 ao2_t_ref(iter->c, -1, "Unref iterator in ao2_iterator_destroy");
541 iter->c = NULL;
542
543 /* Free the malloced iterator. */
544 if (iter->flags & AO2_ITERATOR_MALLOCD) {
545 ast_free(iter);
546 }
547}
548
550{
551 if (iter) {
553 }
554}
555
557 const char *tag, const char *file, int line, const char *func)
558{
559 enum ao2_lock_req orig_lock;
560 struct ao2_container_node *node;
561 void *ret;
562
563 if (!__is_ao2_object(iter->c, file, line, func)) {
564 return NULL;
565 }
566
567 if (!iter->c->v_table || !iter->c->v_table->iterator_next) {
568 /* Sanity checks. */
569 __ast_assert_failed(0, "invalid iterator container v_table", file, line, func);
570 return NULL;
571 }
572
573 if (iter->complete) {
574 /* Don't return any more objects. */
575 return NULL;
576 }
577
578 if (iter->flags & AO2_ITERATOR_DONTLOCK) {
579 if (iter->flags & AO2_ITERATOR_UNLINK) {
580 orig_lock = __adjust_lock(iter->c, AO2_LOCK_REQ_WRLOCK, 1);
581 } else {
582 orig_lock = __adjust_lock(iter->c, AO2_LOCK_REQ_RDLOCK, 1);
583 }
584 } else {
585 orig_lock = AO2_LOCK_REQ_MUTEX;
586 if (iter->flags & AO2_ITERATOR_UNLINK) {
587 ao2_wrlock(iter->c);
588 } else {
589 ao2_rdlock(iter->c);
590 }
591 }
592
593 node = iter->c->v_table->iterator_next(iter->c, iter->last_node, iter->flags);
594 if (node) {
595 ret = node->obj;
596
597 if (iter->flags & AO2_ITERATOR_UNLINK) {
598 /* Transfer the object ref from the container to the returned object. */
600
601 /* Transfer the container's node ref to the iterator. */
602 } else {
603 /* Bump ref of returned object */
604 __ao2_ref(ret, +1, tag ?: "Next iterator object.", file, line, func);
605
606 /* Bump the container's node ref for the iterator. */
607 ao2_ref(node, +1);
608 }
609 } else {
610 /* The iteration has completed. */
611 iter->complete = 1;
612 ret = NULL;
613 }
614
615 /* Replace the iterator's node */
616 if (iter->last_node) {
617 ao2_ref(iter->last_node, -1);
618 }
619 iter->last_node = node;
620
621 if (iter->flags & AO2_ITERATOR_DONTLOCK) {
622 __adjust_lock(iter->c, orig_lock, 0);
623 } else {
624 ao2_unlock(iter->c);
625 }
626
627 return ret;
628}
629
631{
632 return ao2_container_count(iter->c);
633}
634
635void container_destruct(void *_c)
636{
637 struct ao2_container *c = _c;
638
639 /* Unlink any stored objects in the container. */
640 c->destroying = 1;
642 "container_destruct called");
643
644 /* Perform any extra container cleanup. */
645 if (c->v_table && c->v_table->destroy) {
646 c->v_table->destroy(c);
647 }
648
649#if defined(AO2_DEBUG)
650 ast_atomic_fetchadd_int(&ao2.total_containers, -1);
651#endif
652}
653
654/*!
655 * \internal
656 * \brief Put obj into the arg container.
657 * \since 11.0
658 *
659 * \param obj pointer to the (user-defined part) of an object.
660 * \param arg callback argument from ao2_callback()
661 * \param flags flags from ao2_callback()
662 *
663 * \retval 0 on success.
664 * \retval CMP_STOP|CMP_MATCH on error.
665 */
666static int dup_obj_cb(void *obj, void *arg, int flags)
667{
668 struct ao2_container *dest = arg;
669
670 return ao2_link_flags(dest, obj, OBJ_NOLOCK) ? 0 : (CMP_MATCH | CMP_STOP);
671}
672
673int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags)
674{
675 void *obj;
676 int res = 0;
677
678 if (!(flags & OBJ_NOLOCK)) {
679 ao2_rdlock(src);
680 ao2_wrlock(dest);
681 }
682 obj = ao2_callback(src, OBJ_NOLOCK, dup_obj_cb, dest);
683 if (obj) {
684 /* Failed to put this obj into the dest container. */
685 ao2_t_ref(obj, -1, "Failed to put this object into the dest container.");
686
687 /* Remove all items from the dest container. */
689 NULL);
690 res = -1;
691 }
692 if (!(flags & OBJ_NOLOCK)) {
693 ao2_unlock(dest);
694 ao2_unlock(src);
695 }
696
697 return res;
698}
699
700/*!
701 * \brief Copy obj associated with a weakproxy into the arg container.
702 *
703 * \param proxy pointer to the weakproxy.
704 * \param arg callback argument from ao2_callback()
705 * \param flags flags from ao2_callback()
706 *
707 * \retval 0 on success.
708 * \retval CMP_STOP|CMP_MATCH on error.
709 */
710static int dup_weakproxy_cb(void *proxy, void *arg, int flags)
711{
712 void *obj = ao2_weakproxy_get_object(proxy, 0);
713 struct ao2_container *dest = arg;
714 int ret;
715
716 if (!obj) {
717 return 0;
718 }
719
720 ret = ao2_link_flags(dest, obj, OBJ_NOLOCK) ? 0 : (CMP_MATCH | CMP_STOP);
721 ao2_ref(obj, -1);
722
723 return ret;
724}
725
727{
728 void *obj;
729 int res = 0;
730
731 if (!(flags & OBJ_NOLOCK)) {
732 ao2_rdlock(src);
733 ao2_wrlock(dest);
734 }
735 obj = ao2_callback(src, OBJ_NOLOCK, dup_weakproxy_cb, dest);
736 if (obj) {
737 /* Failed to put this obj into the dest container. */
738 ao2_t_ref(obj, -1, "Failed to put this object into the dest container.");
739
740 /* Remove all items from the dest container. */
742 NULL);
743 res = -1;
744 }
745 if (!(flags & OBJ_NOLOCK)) {
746 ao2_unlock(dest);
747 ao2_unlock(src);
748 }
749
750 return res;
751}
752
753struct ao2_container *__ao2_container_clone(struct ao2_container *orig, enum search_flags flags, const char *tag, const char *file, int line, const char *func)
754{
755 struct ao2_container *clone;
756 int failed;
757
758 /* Create the clone container with the same properties as the original. */
759 if (!__is_ao2_object(orig, file, line, func)) {
760 return NULL;
761 }
762
763 if (!orig->v_table || !orig->v_table->alloc_empty_clone) {
764 /* Sanity checks. */
765 __ast_assert_failed(0, "invalid container v_table", file, line, func);
766 return NULL;
767 }
768
769 clone = orig->v_table->alloc_empty_clone(orig, tag, file, line, func);
770 if (!clone) {
771 return NULL;
772 }
773
774 /* This test is correct. clone must be locked before calling
775 * ao2_container_dup when the OBJ_NOLOCK flag is set, otherwise
776 * we could have errors in __adjust_lock. */
777 if (flags & OBJ_NOLOCK) {
778 ao2_wrlock(clone);
779 }
780 failed = ao2_container_dup(clone, orig, flags);
781 if (flags & OBJ_NOLOCK) {
782 ao2_unlock(clone);
783 }
784 if (failed) {
785 /* Object copy into the clone container failed. */
786 __ao2_ref(clone, -1, tag ?: "Clone creation failed", file, line, func);
787 clone = NULL;
788 }
789 return clone;
790}
791
792void ao2_container_dump(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj)
793{
794 if (!is_ao2_object(self) || !self->v_table) {
795 prnt(where, "Invalid container\n");
796 ast_assert(0);
797 return;
798 }
799
800 if (!(flags & OBJ_NOLOCK)) {
801 ao2_rdlock(self);
802 }
803 if (name) {
804 prnt(where, "Container name: %s\n", name);
805 }
806#if defined(AO2_DEBUG)
807 if (self->v_table->dump) {
808 self->v_table->dump(self, where, prnt, prnt_obj);
809 } else
810#endif /* defined(AO2_DEBUG) */
811 {
812 prnt(where, "Container dump not available.\n");
813 }
814 if (!(flags & OBJ_NOLOCK)) {
815 ao2_unlock(self);
816 }
817}
818
819void ao2_container_stats(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt)
820{
821 if (!is_ao2_object(self) || !self->v_table) {
822 prnt(where, "Invalid container\n");
823 ast_assert(0);
824 return;
825 }
826
827 if (!(flags & OBJ_NOLOCK)) {
828 ao2_rdlock(self);
829 }
830 if (name) {
831 prnt(where, "Container name: %s\n", name);
832 }
833 prnt(where, "Number of objects: %d\n", self->elements);
834#if defined(AO2_DEBUG)
835 prnt(where, "Number of nodes: %d\n", self->nodes);
836 prnt(where, "Number of empty nodes: %d\n", self->nodes - self->elements);
837 /*
838 * XXX
839 * If the max_empty_nodes count gets out of single digits you
840 * likely have a code path where ao2_iterator_destroy() is not
841 * called.
842 *
843 * Empty nodes do not harm the container but they do make
844 * container operations less efficient.
845 */
846 prnt(where, "Maximum empty nodes: %d\n", self->max_empty_nodes);
847 if (self->v_table->stats) {
848 self->v_table->stats(self, where, prnt);
849 }
850#endif /* defined(AO2_DEBUG) */
851 if (!(flags & OBJ_NOLOCK)) {
852 ao2_unlock(self);
853 }
854}
855
856int ao2_container_check(struct ao2_container *self, enum search_flags flags)
857{
858 int res = 0;
859
860 if (!is_ao2_object(self) || !self->v_table) {
861 /* Sanity checks. */
862 ast_assert(0);
863 return -1;
864 }
865#if defined(AO2_DEBUG)
866 if (!self->v_table->integrity) {
867 /* No integrity check available. Assume container is ok. */
868 return 0;
869 }
870
871 if (!(flags & OBJ_NOLOCK)) {
872 ao2_rdlock(self);
873 }
874 res = self->v_table->integrity(self);
875 if (!(flags & OBJ_NOLOCK)) {
876 ao2_unlock(self);
877 }
878#endif /* defined(AO2_DEBUG) */
879 return res;
880}
881
882#if defined(AO2_DEBUG)
883static struct ao2_container *reg_containers;
884
885struct ao2_reg_container {
886 /*! Registered container pointer. */
887 struct ao2_container *registered;
888 /*! Callback function to print the given object's key. (NULL if not available) */
889 ao2_prnt_obj_fn *prnt_obj;
890 /*! Name container registered under. */
891 char name[1];
892};
893
894struct ao2_reg_partial_key {
895 /*! Length of partial key match. */
896 int len;
897 /*! Registration partial key name. */
898 const char *name;
899};
900
901struct ao2_reg_match {
902 /*! The nth match to find. */
903 int find_nth;
904 /*! Count of the matches already found. */
905 int count;
906};
907#endif /* defined(AO2_DEBUG) */
908
909#if defined(AO2_DEBUG)
910static int ao2_reg_sort_cb(const void *obj_left, const void *obj_right, int flags)
911{
912 const struct ao2_reg_container *reg_left = obj_left;
913 int cmp;
914
915 switch (flags & OBJ_SEARCH_MASK) {
917 {
918 const struct ao2_reg_container *reg_right = obj_right;
919
920 cmp = strcasecmp(reg_left->name, reg_right->name);
921 }
922 break;
923 case OBJ_SEARCH_KEY:
924 {
925 const char *name = obj_right;
926
927 cmp = strcasecmp(reg_left->name, name);
928 }
929 break;
931 {
932 const struct ao2_reg_partial_key *partial_key = obj_right;
933
934 cmp = strncasecmp(reg_left->name, partial_key->name, partial_key->len);
935 }
936 break;
937 default:
938 /* Sort can only work on something with a full or partial key. */
939 ast_assert(0);
940 cmp = 0;
941 break;
942 }
943 return cmp;
944}
945#endif /* defined(AO2_DEBUG) */
946
947#if defined(AO2_DEBUG)
948static void ao2_reg_destructor(void *v_doomed)
949{
950 struct ao2_reg_container *doomed = v_doomed;
951
952 if (doomed->registered) {
953 ao2_t_ref(doomed->registered, -1, "Releasing registered container.");
954 }
955}
956#endif /* defined(AO2_DEBUG) */
957
958int ao2_container_register(const char *name, struct ao2_container *self, ao2_prnt_obj_fn *prnt_obj)
959{
960 int res = 0;
961#if defined(AO2_DEBUG)
962 struct ao2_reg_container *reg;
963
964 reg = ao2_t_alloc_options(sizeof(*reg) + strlen(name), ao2_reg_destructor,
965 AO2_ALLOC_OPT_LOCK_NOLOCK, "Container registration object.");
966 if (!reg) {
967 return -1;
968 }
969
970 /* Fill in registered entry */
971 ao2_t_ref(self, +1, "Registering container.");
972 reg->registered = self;
973 reg->prnt_obj = prnt_obj;
974 strcpy(reg->name, name);/* safe */
975
976 if (!ao2_t_link(reg_containers, reg, "Save registration object.")) {
977 res = -1;
978 }
979
980 ao2_t_ref(reg, -1, "Done registering container.");
981#endif /* defined(AO2_DEBUG) */
982 return res;
983}
984
986{
987#if defined(AO2_DEBUG)
989 "Unregister container");
990#endif /* defined(AO2_DEBUG) */
991}
992
993#if defined(AO2_DEBUG)
994static int ao2_complete_reg_cb(void *obj, void *arg, int flags)
995{
996 struct ao2_reg_container *reg = obj;
997
998 if (ast_cli_completion_add(ast_strdup(reg->name))) {
999 return CMP_STOP;
1000 }
1001
1002 return 0;
1003}
1004#endif /* defined(AO2_DEBUG) */
1005
1006#if defined(AO2_DEBUG)
1007static char *complete_container_names(struct ast_cli_args *a)
1008{
1009 struct ao2_reg_partial_key partial_key;
1010
1011 if (a->pos != 3) {
1012 return NULL;
1013 }
1014
1015 partial_key.len = strlen(a->word);
1016 partial_key.name = a->word;
1017 ao2_callback(reg_containers, partial_key.len ? OBJ_SEARCH_PARTIAL_KEY : 0,
1018 ao2_complete_reg_cb, &partial_key);
1019
1020 return NULL;
1021}
1022#endif /* defined(AO2_DEBUG) */
1023
1024#if defined(AO2_DEBUG)
1025AST_THREADSTORAGE(ao2_out_buf);
1026
1027/*!
1028 * \brief Print CLI output.
1029 * \since 12.0.0
1030 *
1031 * \param where User data pointer needed to determine where to put output.
1032 * \param fmt printf type format string.
1033 */
1034static void cli_output(void *where, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
1035static void cli_output(void *where, const char *fmt, ...)
1036{
1037 int res;
1038 struct ast_str *buf;
1039 va_list ap;
1040
1041 buf = ast_str_thread_get(&ao2_out_buf, 256);
1042 if (!buf) {
1043 return;
1044 }
1045
1046 va_start(ap, fmt);
1047 res = ast_str_set_va(&buf, 0, fmt, ap);
1048 va_end(ap);
1049
1050 if (res != AST_DYNSTR_BUILD_FAILED) {
1051 ast_cli(*(int *) where, "%s", ast_str_buffer(buf));
1052 }
1053}
1054#endif /* defined(AO2_DEBUG) */
1055
1056#if defined(AO2_DEBUG)
1057/*! \brief Show container contents - CLI command */
1058static char *handle_cli_astobj2_container_dump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1059{
1060 const char *name;
1061 struct ao2_reg_container *reg;
1062
1063 switch (cmd) {
1064 case CLI_INIT:
1065 e->command = "astobj2 container dump";
1066 e->usage =
1067 "Usage: astobj2 container dump <name>\n"
1068 " Show contents of the container <name>.\n";
1069 return NULL;
1070 case CLI_GENERATE:
1071 return complete_container_names(a);
1072 }
1073
1074 if (a->argc != 4) {
1075 return CLI_SHOWUSAGE;
1076 }
1077
1078 name = a->argv[3];
1079 reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
1080 if (reg) {
1081 ao2_container_dump(reg->registered, 0, name, (void *) &a->fd, cli_output,
1082 reg->prnt_obj);
1083 ao2_t_ref(reg, -1, "Done with registered container object.");
1084 } else {
1085 ast_cli(a->fd, "Container '%s' not found.\n", name);
1086 }
1087
1088 return CLI_SUCCESS;
1089}
1090#endif /* defined(AO2_DEBUG) */
1091
1092#if defined(AO2_DEBUG)
1093/*! \brief Show container statistics - CLI command */
1094static char *handle_cli_astobj2_container_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1095{
1096 const char *name;
1097 struct ao2_reg_container *reg;
1098
1099 switch (cmd) {
1100 case CLI_INIT:
1101 e->command = "astobj2 container stats";
1102 e->usage =
1103 "Usage: astobj2 container stats <name>\n"
1104 " Show statistics about the specified container <name>.\n";
1105 return NULL;
1106 case CLI_GENERATE:
1107 return complete_container_names(a);
1108 }
1109
1110 if (a->argc != 4) {
1111 return CLI_SHOWUSAGE;
1112 }
1113
1114 name = a->argv[3];
1115 reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
1116 if (reg) {
1117 ao2_container_stats(reg->registered, 0, name, (void *) &a->fd, cli_output);
1118 ao2_t_ref(reg, -1, "Done with registered container object.");
1119 } else {
1120 ast_cli(a->fd, "Container '%s' not found.\n", name);
1121 }
1122
1123 return CLI_SUCCESS;
1124}
1125#endif /* defined(AO2_DEBUG) */
1126
1127#if defined(AO2_DEBUG)
1128/*! \brief Show container check results - CLI command */
1129static char *handle_cli_astobj2_container_check(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1130{
1131 const char *name;
1132 struct ao2_reg_container *reg;
1133
1134 switch (cmd) {
1135 case CLI_INIT:
1136 e->command = "astobj2 container check";
1137 e->usage =
1138 "Usage: astobj2 container check <name>\n"
1139 " Perform a container integrity check on <name>.\n";
1140 return NULL;
1141 case CLI_GENERATE:
1142 return complete_container_names(a);
1143 }
1144
1145 if (a->argc != 4) {
1146 return CLI_SHOWUSAGE;
1147 }
1148
1149 name = a->argv[3];
1150 reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
1151 if (reg) {
1152 ast_cli(a->fd, "Container check of '%s': %s.\n", name,
1153 ao2_container_check(reg->registered, 0) ? "failed" : "OK");
1154 ao2_t_ref(reg, -1, "Done with registered container object.");
1155 } else {
1156 ast_cli(a->fd, "Container '%s' not found.\n", name);
1157 }
1158
1159 return CLI_SUCCESS;
1160}
1161#endif /* defined(AO2_DEBUG) */
1162
1163#if defined(AO2_DEBUG)
1164static struct ast_cli_entry cli_astobj2[] = {
1165 AST_CLI_DEFINE(handle_cli_astobj2_container_dump, "Show container contents"),
1166 AST_CLI_DEFINE(handle_cli_astobj2_container_stats, "Show container statistics"),
1167 AST_CLI_DEFINE(handle_cli_astobj2_container_check, "Perform a container integrity check"),
1168};
1169#endif /* defined(AO2_DEBUG) */
1170
1171#if defined(AO2_DEBUG)
1172static void container_cleanup(void)
1173{
1174 ao2_t_ref(reg_containers, -1, "Releasing container registration container");
1175 reg_containers = NULL;
1176
1177 ast_cli_unregister_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2));
1178}
1179#endif /* defined(AO2_DEBUG) */
1180
1182{
1183#if defined(AO2_DEBUG)
1186 "Container registration container.");
1187 if (!reg_containers) {
1188 return -1;
1189 }
1190
1191 ast_cli_register_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2));
1192 ast_register_cleanup(container_cleanup);
1193#endif /* defined(AO2_DEBUG) */
1194
1195 return 0;
1196}
Prototypes for public functions only of internal interest,.
Asterisk main include file. File version handling, generic pbx functions.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition: clicompat.c:19
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
enum ao2_lock_req __adjust_lock(void *user_data, enum ao2_lock_req lock_how, int keep_stronger)
Definition: astobj2.c:425
#define ast_log
Definition: astobj2.c:42
void() ao2_prnt_obj_fn(void *v_obj, void *where, ao2_prnt_fn *prnt)
Print object key.
Definition: astobj2.h:1445
#define ao2_t_ref(o, delta, tag)
Definition: astobj2.h:460
@ CMP_MATCH
Definition: astobj2.h:1027
@ CMP_STOP
Definition: astobj2.h:1028
#define ao2_rdlock(a)
Definition: astobj2.h:718
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition: astobj2.h:367
@ AO2_ALLOC_OPT_LOCK_RWLOCK
Definition: astobj2.h:365
#define ao2_wrlock(a)
Definition: astobj2.h:719
#define ao2_t_find(container, arg, flags, tag)
Definition: astobj2.h:1734
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition: astobj2.h:1693
ao2_lock_req
Which lock to request.
Definition: astobj2.h:700
@ AO2_LOCK_REQ_MUTEX
Definition: astobj2.h:702
@ AO2_LOCK_REQ_WRLOCK
Definition: astobj2.h:706
@ AO2_LOCK_REQ_RDLOCK
Definition: astobj2.h:704
#define ao2_t_link(container, obj, tag)
Definition: astobj2.h:1534
void * __ao2_weakproxy_get_object(void *weakproxy, int flags, const char *tag, const char *file, int line, const char *func) attribute_warn_unused_result
Definition: astobj2.c:889
#define ao2_unlink_flags(container, obj, flags)
Remove an object from a container.
Definition: astobj2.h:1600
int() ao2_callback_fn(void *obj, void *arg, int flags)
Type of a generic callback function.
Definition: astobj2.h:1226
@ AO2_ITERATOR_UNLINK
Definition: astobj2.h:1863
@ AO2_ITERATOR_DONTLOCK
Assume that the ao2_container is already locked.
Definition: astobj2.h:1852
@ AO2_ITERATOR_MALLOCD
Definition: astobj2.h:1857
#define ao2_link_flags(container, obj, flags)
Add an object to a container.
Definition: astobj2.h:1554
#define ao2_t_alloc_options(data_size, destructor_fn, options, debug_msg)
Allocate and initialize an object.
Definition: astobj2.h:402
#define ao2_find(container, arg, flags)
Definition: astobj2.h:1736
#define ao2_t_container_alloc_list(ao2_options, container_options, sort_fn, cmp_fn, tag)
Definition: astobj2.h:1330
#define ao2_unlock(a)
Definition: astobj2.h:729
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_weakproxy_get_object(weakproxy, flags)
Get the object associated with weakproxy.
Definition: astobj2.h:621
int() ao2_callback_data_fn(void *obj, void *arg, void *data, int flags)
Type of a generic callback function.
Definition: astobj2.h:1244
int __ao2_ref(void *o, int delta, const char *tag, const char *file, int line, const char *func)
Definition: astobj2.c:498
search_flags
Flags passed to ao2_callback_fn(), ao2_hash_fn(), and ao2_sort_fn() to modify behaviour.
Definition: astobj2.h:1034
@ OBJ_SEARCH_PARTIAL_KEY
The arg parameter is a partial search key similar to OBJ_SEARCH_KEY.
Definition: astobj2.h:1116
@ OBJ_SEARCH_OBJECT
The arg parameter is an object of the same type.
Definition: astobj2.h:1087
@ OBJ_NOLOCK
Assume that the ao2_container is already locked.
Definition: astobj2.h:1063
@ OBJ_NODATA
Definition: astobj2.h:1044
@ OBJ_SEARCH_MASK
Search option field mask.
Definition: astobj2.h:1072
@ OBJ_MULTIPLE
Definition: astobj2.h:1049
@ OBJ_UNLINK
Definition: astobj2.h:1039
@ OBJ_SEARCH_KEY
The arg parameter is a search key, but is not an object.
Definition: astobj2.h:1101
#define ao2_t_callback(c, flags, cb_fn, arg, tag)
Definition: astobj2.h:1696
void() ao2_prnt_fn(void *where, const char *fmt,...)
Print output.
Definition: astobj2.h:1435
@ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE
Replace objects with duplicate keys in container.
Definition: astobj2.h:1211
int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags)
Copy all object references in the src container into the dest container.
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags)
Create an iterator for a container.
void ao2_iterator_cleanup(struct ao2_iterator *iter)
int ao2_container_dup_weakproxy_objs(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags)
Copy object references associated with src container weakproxies into the dest container.
int ao2_container_check(struct ao2_container *self, enum search_flags flags)
Perform an integrity check on the specified container.
void * __ao2_callback_data(struct ao2_container *c, enum search_flags flags, ao2_callback_data_fn *cb_fn, void *arg, void *data, const char *tag, const char *file, int line, const char *func)
int __ao2_link(struct ao2_container *self, void *obj_new, int flags, const char *tag, const char *file, int line, const char *func)
void ao2_container_unregister(const char *name)
Unregister a container for CLI stats and integrity check.
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
static int dup_obj_cb(void *obj, void *arg, int flags)
static void * internal_ao2_traverse(struct ao2_container *self, enum search_flags flags, void *cb_fn, void *arg, void *data, enum ao2_callback_type type, const char *tag, const char *file, int line, const char *func)
static int cb_true_data(void *user_data, void *arg, void *data, int flags)
similar to cb_true, but is an ao2_callback_data_fn instead
void * __ao2_callback(struct ao2_container *c, enum search_flags flags, ao2_callback_fn *cb_fn, void *arg, const char *tag, const char *file, int line, const char *func)
void ao2_container_stats(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt)
Display statistics of the specified container.
static int dup_weakproxy_cb(void *proxy, void *arg, int flags)
Copy obj associated with a weakproxy into the arg container.
struct ao2_container * __ao2_container_clone(struct ao2_container *orig, enum search_flags flags, const char *tag, const char *file, int line, const char *func)
int ao2_iterator_count(struct ao2_iterator *iter)
Get a count of the iterated container objects.
void ao2_iterator_restart(struct ao2_iterator *iter)
Restart an iteration.
int container_init(void)
void * __ao2_find(struct ao2_container *c, const void *arg, enum search_flags flags, const char *tag, const char *file, int line, const char *func)
static int cb_true(void *user_data, void *arg, int flags)
special callback that matches all
void * __ao2_weakproxy_find(struct ao2_container *c, const void *arg, enum search_flags flags, const char *tag, const char *file, int line, const char *func)
void container_destruct(void *_c)
void * __ao2_iterator_next(struct ao2_iterator *iter, const char *tag, const char *file, int line, const char *func)
int ao2_container_register(const char *name, struct ao2_container *self, ao2_prnt_obj_fn *prnt_obj)
Register a container for CLI stats and integrity check.
void * __ao2_unlink(struct ao2_container *c, void *user_data, int flags, const char *tag, const char *file, int line, const char *func)
void ao2_container_dump(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj)
Display contents of the specified container.
int ao2_match_by_addr(void *user_data, void *arg, int flags)
another convenience function is a callback that matches on address
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
int __container_unlink_node_debug(struct ao2_container_node *node, uint32_t flags, const char *tag, const char *file, int line, const char *func)
Common, private definitions for astobj2 containers.
@ AO2_CALLBACK_DEFAULT
@ AO2_CALLBACK_WITH_DATA
#define AO2_TRAVERSAL_STATE_SIZE
@ AO2_CONTAINER_INSERT_NODE_OBJ_REPLACED
@ AO2_CONTAINER_INSERT_NODE_REJECTED
@ AO2_CONTAINER_INSERT_NODE_INSERTED
@ AO2_UNLINK_NODE_UNREF_NODE
@ AO2_UNLINK_NODE_UNLINK_OBJECT
@ AO2_UNLINK_NODE_DEC_COUNT
@ AO2_UNLINK_NODE_NOUNREF_OBJECT
Common, private definitions for astobj2.
#define is_ao2_object(user_data)
#define AO2_DEVMODE_STAT(stat)
#define __is_ao2_object(user_data, file, line, func)
static int match(struct ast_sockaddr *addr, unsigned short callno, unsigned short dcallno, const struct chan_iax2_pvt *cur, int check_dcallno)
Definition: chan_iax2.c:2365
static const char type[]
Definition: chan_ooh323.c:109
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition: cli.h:45
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
int ast_cli_completion_add(char *value)
Add a result to a request for completion options.
Definition: main/cli.c:2758
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static const char name[]
Definition: format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define LOG_ERROR
int ast_atomic_fetchadd_int(volatile int *p, int v)
Atomically add v to *p and return the previous value of *p.
Definition: lock.h:757
struct ao2_container * container
Definition: res_fax.c:501
#define NULL
Definition: resample.c:96
@ AST_DYNSTR_BUILD_FAILED
Definition: strings.h:943
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
int ast_str_set_va(struct ast_str **buf, ssize_t max_len, const char *fmt, va_list ap)
Set a dynamic string from a va_list.
Definition: strings.h:1030
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
ao2_container_insert_fn insert
ao2_container_alloc_empty_clone_fn alloc_empty_clone
Create an empty copy of this container.
ao2_iterator_next_fn iterator_next
ao2_container_find_cleanup_fn traverse_cleanup
ao2_container_new_node_fn new_node
ao2_container_find_next_fn traverse_next
ao2_container_find_first_fn traverse_first
Generic container node.
Generic container type.
const struct ao2_container_methods * v_table
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition: astobj2.h:1821
int complete
Definition: astobj2.h:1827
struct ao2_container * c
Definition: astobj2.h:1823
void * last_node
Definition: astobj2.h:1825
descriptor for a cli entry.
Definition: cli.h:171
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Support for dynamic strings.
Definition: strings.h:623
Definition: test_heap.c:38
static struct test_val a
static struct test_val c
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
#define ast_assert(a)
Definition: utils.h:739
#define ARRAY_LEN(a)
Definition: utils.h:666
void DO_CRASH_NORETURN __ast_assert_failed(int condition, const char *condition_str, const char *file, int line, const char *function)
Definition: utils.c:2816