Asterisk - The Open Source Telephony Project  18.5.0
pickup.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2013, Digium, Inc.
5  * Copyright (C) 2012, Russell Bryant
6  *
7  * Matt Jordan <[email protected]>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19 
20 /*! \file
21  *
22  * \brief Routines implementing call pickup
23  *
24  * \author Matt Jordan <[email protected]>
25  */
26 
27 /*!
28  * \li Call pickup uses the configuration file \ref features.conf
29  * \addtogroup configuration_file Configuration Files
30  */
31 
32 /*** MODULEINFO
33  <support_level>core</support_level>
34  ***/
35 
36 /*** DOCUMENTATION
37  <managerEvent language="en_US" name="Pickup">
38  <managerEventInstance class="EVENT_FLAG_CALL">
39  <synopsis>Raised when a call pickup occurs.</synopsis>
40  <syntax>
41  <channel_snapshot/>
42  <channel_snapshot prefix="Target"/>
43  </syntax>
44  </managerEventInstance>
45  </managerEvent>
46  ***/
47 
48 #include "asterisk.h"
49 
50 #include "asterisk/pickup.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/pbx.h"
53 #include "asterisk/app.h"
54 #include "asterisk/callerid.h"
55 #include "asterisk/causes.h"
56 #include "asterisk/stasis.h"
59 
61 
65 
66 
67 /*!
68  * The presence of this datastore on the channel indicates that
69  * someone is attemting to pickup or has picked up the channel.
70  * The purpose is to prevent a race between two channels
71  * attempting to pickup the same channel.
72  */
73 static const struct ast_datastore_info pickup_active = {
74  .type = "pickup-active",
75 };
76 
77 int ast_can_pickup(struct ast_channel *chan)
78 {
82  /*
83  * Check the down state as well because some SIP devices do not
84  * give 180 ringing when they can just give 183 session progress
85  * instead. Issue 14005. (Some ISDN switches as well for that
86  * matter.)
87  */
89  && !ast_channel_datastore_find(chan, &pickup_active, NULL)) {
90  return 1;
91  }
92  return 0;
93 }
94 
95 static int find_channel_by_group(void *obj, void *arg, void *data, int flags)
96 {
97  struct ast_channel *target = obj; /*!< Potential pickup target */
98  struct ast_channel *chan = arg; /*!< Channel wanting to pickup call */
99 
100  if (chan == target) {
101  return 0;
102  }
103 
104  ast_channel_lock(target);
105  if (ast_can_pickup(target)) {
106  /* Lock both channels. */
107  while (ast_channel_trylock(chan)) {
108  ast_channel_unlock(target);
109  sched_yield();
110  ast_channel_lock(target);
111  }
112 
113  /*
114  * Both callgroup and namedcallgroup pickup variants are
115  * matched independently. Checking for named group match is
116  * done last since it's a more expensive operation.
117  */
118  if ((ast_channel_pickupgroup(chan) & ast_channel_callgroup(target))
120  ast_channel_named_callgroups(target)))) {
121  struct ao2_container *candidates = data;/*!< Candidate channels found. */
122 
123  /* This is a candidate to pickup */
124  ao2_link(candidates, target);
125  }
126  ast_channel_unlock(chan);
127  }
128  ast_channel_unlock(target);
129 
130  return 0;
131 }
132 
134 {
135  struct ao2_container *candidates;/*!< Candidate channels found to pickup. */
136  struct ast_channel *target;/*!< Potential pickup target */
137 
139  if (!candidates) {
140  return NULL;
141  }
142 
143  /* Find all candidate targets by group. */
144  ast_channel_callback(find_channel_by_group, chan, candidates, 0);
145 
146  /* Find the oldest pickup target candidate */
147  target = NULL;
148  for (;;) {
149  struct ast_channel *candidate;/*!< Potential new older target */
150  struct ao2_iterator iter;
151 
152  iter = ao2_iterator_init(candidates, 0);
153  while ((candidate = ao2_iterator_next(&iter))) {
154  if (!target) {
155  /* First target. */
156  target = candidate;
157  continue;
158  }
159  if (ast_tvcmp(ast_channel_creationtime(candidate), ast_channel_creationtime(target)) < 0) {
160  /* We have a new target. */
161  ast_channel_unref(target);
162  target = candidate;
163  continue;
164  }
165  ast_channel_unref(candidate);
166  }
167  ao2_iterator_destroy(&iter);
168  if (!target) {
169  /* No candidates found. */
170  break;
171  }
172 
173  /* The found channel must be locked and ref'd. */
174  ast_channel_lock(target);
175 
176  /* Recheck pickup ability */
177  if (ast_can_pickup(target)) {
178  /* This is the channel to pickup. */
179  break;
180  }
181 
182  /* Someone else picked it up or the call went away. */
183  ast_channel_unlock(target);
184  ao2_unlink(candidates, target);
185  target = ast_channel_unref(target);
186  }
187  ao2_ref(candidates, -1);
188 
189  return target;
190 }
191 
192 /*!
193  * \brief Pickup a call
194  * \param chan channel that initiated pickup.
195  *
196  * Walk list of channels, checking it is not itself, channel is pbx one,
197  * check that the callgroup for both channels are the same and the channel is ringing.
198  * Answer calling channel, flag channel as answered on queue, masq channels together.
199  */
200 int ast_pickup_call(struct ast_channel *chan)
201 {
202  struct ast_channel *target;/*!< Potential pickup target */
203  int res = -1;
204  RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
205  const char *pickup_sound;
206  const char *fail_sound;
207 
208  ast_debug(1, "Pickup attempt by %s\n", ast_channel_name(chan));
209  ast_channel_lock(chan);
210  pickup_cfg = ast_get_chan_features_pickup_config(chan);
211  if (!pickup_cfg) {
212  ast_log(LOG_ERROR, "Unable to retrieve pickup configuration. Unable to play pickup sounds\n");
213  }
214  pickup_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupsound : "");
215  fail_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupfailsound : "");
216  ast_channel_unlock(chan);
217 
218  /* The found channel is already locked. */
219  target = ast_pickup_find_by_group(chan);
220  if (target) {
221  ast_log(LOG_NOTICE, "Pickup %s attempt by %s\n", ast_channel_name(target), ast_channel_name(chan));
222 
223  res = ast_do_pickup(chan, target);
224  ast_channel_unlock(target);
225  if (!res) {
226  if (!ast_strlen_zero(pickup_sound)) {
227  pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickup_sound);
228  }
229  } else {
230  ast_log(LOG_WARNING, "Pickup %s failed by %s\n", ast_channel_name(target), ast_channel_name(chan));
231  }
232  target = ast_channel_unref(target);
233  }
234 
235  if (res < 0) {
236  ast_debug(1, "No call pickup possible... for %s\n", ast_channel_name(chan));
237  if (!ast_strlen_zero(fail_sound)) {
238  ast_answer(chan);
239  ast_stream_and_wait(chan, fail_sound, "");
240  }
241  }
242 
243  return res;
244 }
245 
247 {
249  struct ast_channel_snapshot *chan;
250  struct ast_channel_snapshot *target;
251  struct ast_manager_event_blob *res;
252 
253  RAII_VAR(struct ast_str *, channel_str, NULL, ast_free);
254  RAII_VAR(struct ast_str *, target_str, NULL, ast_free);
255 
256  chan = ast_multi_channel_blob_get_channel(contents, "channel");
257  target = ast_multi_channel_blob_get_channel(contents, "target");
258 
259  ast_assert(chan != NULL && target != NULL);
260 
261  if (!(channel_str = ast_manager_build_channel_state_string(chan))) {
262  return NULL;
263  }
264 
265  if (!(target_str = ast_manager_build_channel_state_string_prefix(target, "Target"))) {
266  return NULL;
267  }
268 
270  "%s"
271  "%s",
272  ast_str_buffer(channel_str),
273  ast_str_buffer(target_str));
274 
275  return res;
276 }
277 
278 static int send_call_pickup_stasis_message(struct ast_channel *picking_up, struct ast_channel_snapshot *chan, struct ast_channel_snapshot *target)
279 {
280  RAII_VAR(struct ast_multi_channel_blob *, pickup_payload, NULL, ao2_cleanup);
281  RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
282 
283  if (!ast_call_pickup_type()) {
284  return -1;
285  }
286 
287  if (!(pickup_payload = ast_multi_channel_blob_create(ast_json_null()))) {
288  return -1;
289  }
290 
291  ast_multi_channel_blob_add_channel(pickup_payload, "channel", chan);
292  ast_multi_channel_blob_add_channel(pickup_payload, "target", target);
293 
294  if (!(msg = stasis_message_create(ast_call_pickup_type(), pickup_payload))) {
295  return -1;
296  }
297 
298  stasis_publish(ast_channel_topic(picking_up), msg);
299  return 0;
300 }
301 
302 int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target)
303 {
304  struct ast_party_connected_line connected_caller;
305  struct ast_datastore *ds_pickup;
306  const char *chan_name;/*!< A masquerade changes channel names. */
307  const char *target_name;/*!< A masquerade changes channel names. */
308  int res = -1;
309 
310  RAII_VAR(struct ast_channel_snapshot *, chan_snapshot, NULL, ao2_cleanup);
311  RAII_VAR(struct ast_channel_snapshot *, target_snapshot, NULL, ao2_cleanup);
312 
313  target_name = ast_strdupa(ast_channel_name(target));
314  ast_debug(1, "Call pickup on '%s' by '%s'\n", target_name, ast_channel_name(chan));
315 
316  /* Mark the target to block any call pickup race. */
317  ds_pickup = ast_datastore_alloc(&pickup_active, NULL);
318  if (!ds_pickup) {
320  "Unable to create channel datastore on '%s' for call pickup\n", target_name);
321  return -1;
322  }
323  ast_channel_datastore_add(target, ds_pickup);
324 
325  ast_party_connected_line_init(&connected_caller);
326  ast_party_connected_line_copy(&connected_caller, ast_channel_connected(target));
327  ast_channel_unlock(target);/* The pickup race is avoided so we do not need the lock anymore. */
328  /* Reset any earlier private connected id representation */
329  ast_party_id_reset(&connected_caller.priv);
330 
332  if (ast_channel_connected_line_sub(NULL, chan, &connected_caller, 0) &&
333  ast_channel_connected_line_macro(NULL, chan, &connected_caller, 0, 0)) {
334  ast_channel_update_connected_line(chan, &connected_caller, NULL);
335  }
336  ast_party_connected_line_free(&connected_caller);
337 
338  ast_channel_lock(chan);
339  chan_name = ast_strdupa(ast_channel_name(chan));
341  ast_channel_unlock(chan);
343 
344  if (ast_answer(chan)) {
345  ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan_name);
346  goto pickup_failed;
347  }
348 
350  ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan_name);
351  goto pickup_failed;
352  }
353 
354  ast_channel_queue_connected_line_update(chan, &connected_caller, NULL);
355 
356  /* setting the HANGUPCAUSE so the ringing channel knows this call was not a missed call */
358 
359  ast_channel_lock(chan);
360  chan_snapshot = ast_channel_snapshot_create(chan);
361  ast_channel_unlock(chan);
362  if (!chan_snapshot) {
363  goto pickup_failed;
364  }
365 
366  target_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(target));
367  if (!target_snapshot) {
368  goto pickup_failed;
369  }
370 
371  if (ast_channel_move(target, chan)) {
372  ast_log(LOG_WARNING, "Unable to complete call pickup of '%s' with '%s'\n",
373  chan_name, target_name);
374  goto pickup_failed;
375  }
376 
377  /* target points to the channel that did the pickup at this point, so use that channel's topic instead of chan */
378  send_call_pickup_stasis_message(target, chan_snapshot, target_snapshot);
379 
380  res = 0;
381 
382 pickup_failed:
383  ast_channel_lock(target);
384  if (!ast_channel_datastore_remove(target, ds_pickup)) {
385  ast_datastore_free(ds_pickup);
386  }
387  ast_party_connected_line_free(&connected_caller);
388 
389  return res;
390 }
391 
392 /*!
393  * \internal
394  * \brief Clean up resources on Asterisk shutdown
395  */
396 static void pickup_shutdown(void)
397 {
399 }
400 
402 {
405 
406  return 0;
407 }
struct ast_party_caller * ast_channel_caller(struct ast_channel *chan)
const char * type
Definition: datastore.h:32
Struct containing info for an AMI event to send out.
Definition: manager.h:491
void ast_party_connected_line_init(struct ast_party_connected_line *init)
Initialize the given connected line structure.
Definition: channel.c:2022
#define ast_channel_lock(chan)
Definition: channel.h:2945
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const void *connected_info, int is_caller, int frame)
Run a connected line interception macro and update a channel&#39;s connected line information.
Definition: channel.c:10435
struct ast_features_pickup_config * ast_get_chan_features_pickup_config(struct ast_channel *chan)
Get the pickup configuration options for a channel.
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
Queue a control frame without payload.
Definition: channel.c:1231
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
static void pickup_shutdown(void)
Definition: pickup.c:396
void ast_party_id_reset(struct ast_party_id *id)
Destroy and initialize the given party id structure.
Definition: channel.c:1896
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2981
struct ast_channel * ast_pickup_find_by_group(struct ast_channel *chan)
Find a pickup channel target by group.
Definition: pickup.c:133
#define ast_test_flag(p, flag)
Definition: utils.h:63
void ast_channel_hangupcause_set(struct ast_channel *chan, int value)
#define STASIS_MESSAGE_TYPE_INIT(name)
Boiler-plate messaging macro for initializing message types.
Definition: stasis.h:1501
Call Pickup API.
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
#define LOG_WARNING
Definition: logger.h:274
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
void ast_channel_update_connected_line(struct ast_channel *chan, const struct ast_party_connected_line *connected, const struct ast_set_party_connected_line *update)
Indicate that the connected line information has changed.
Definition: channel.c:9189
#define ao2_container_alloc_list(ao2_options, container_options, sort_fn, cmp_fn)
Definition: astobj2.h:1335
Structure representing a snapshot of channel state.
struct ast_str * ast_manager_build_channel_state_string_prefix(const struct ast_channel_snapshot *snapshot, const char *prefix)
Generate the AMI message body from a channel snapshot.
#define STASIS_MESSAGE_TYPE_CLEANUP(name)
Boiler-plate messaging macro for cleaning up message types.
Definition: stasis.h:1523
static int send_call_pickup_stasis_message(struct ast_channel *picking_up, struct ast_channel_snapshot *chan, struct ast_channel_snapshot *target)
Definition: pickup.c:278
#define EVENT_FLAG_CALL
Definition: manager.h:72
Structure for a data store type.
Definition: datastore.h:31
ast_channel_state
ast_channel states
Definition: channelstate.h:35
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
#define ast_assert(a)
Definition: utils.h:695
Structure for a data store object.
Definition: datastore.h:68
void ast_party_connected_line_free(struct ast_party_connected_line *doomed)
Destroy the connected line information contents.
Definition: channel.c:2072
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2404
#define NULL
Definition: resample.c:96
int ast_channel_move(struct ast_channel *dest, struct ast_channel *source)
Move a channel from its current location to a new location.
Definition: channel.c:10867
struct ast_manager_event_blob * ast_manager_event_blob_create(int event_flags, const char *manager_event, const char *extra_fields_fmt,...)
Construct a ast_manager_event_blob.
Definition: manager.c:9727
struct ast_namedgroups * ast_channel_named_callgroups(const struct ast_channel *chan)
struct ast_channel_snapshot * ast_channel_snapshot_create(struct ast_channel *chan)
Generate a snapshot of the channel state. This is an ao2 object, so ao2_cleanup() to deallocate...
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
#define ast_strlen_zero(foo)
Definition: strings.h:52
struct ast_json * ast_json_null(void)
Get the JSON null value.
Definition: json.c:248
static int find_channel_by_group(void *obj, void *arg, void *data, int flags)
Definition: pickup.c:95
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
void ast_channel_queue_connected_line_update(struct ast_channel *chan, const struct ast_party_connected_line *connected, const struct ast_set_party_connected_line *update)
Queue a connected line update frame on a channel.
Definition: channel.c:9202
STASIS_MESSAGE_TYPE_DEFN(ast_call_pickup_type,.to_ami=call_pickup_to_ami)
General Asterisk PBX channel definitions.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition: clicompat.c:19
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:911
struct ast_party_connected_line * ast_channel_connected(struct ast_channel *chan)
struct ast_pbx * ast_channel_pbx(const struct ast_channel *chan)
#define AST_CAUSE_ANSWERED_ELSEWHERE
Definition: causes.h:113
void ast_multi_channel_blob_add_channel(struct ast_multi_channel_blob *obj, const char *role, struct ast_channel_snapshot *snapshot)
Add a ast_channel_snapshot to a ast_multi_channel_blob object.
#define ao2_ref(o, delta)
Definition: astobj2.h:464
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
const char * contents
struct ast_str * ast_manager_build_channel_state_string(const struct ast_channel_snapshot *snapshot)
Generate the AMI message body from a channel snapshot.
Core PBX routines and definitions.
struct ast_channel * ast_channel_callback(ao2_callback_data_fn *cb_fn, void *arg, void *data, int ao2_flags)
Call a function with every active channel.
Definition: channel.c:1278
const char * ast_channel_uniqueid(const struct ast_channel *chan)
struct stasis_topic * ast_channel_topic(struct ast_channel *chan)
A topic which publishes the events for a particular channel.
int ast_pickup_init(void)
Initialize pickup.
Definition: pickup.c:401
struct stasis_message_type * ast_call_pickup_type(void)
accessor for call pickup message type
#define LOG_ERROR
Definition: logger.h:285
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compres two struct timeval instances returning -1, 0, 1 if the first arg is smaller, equal or greater to the second.
Definition: time.h:128
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
static struct ast_manager_event_blob * call_pickup_to_ami(struct stasis_message *message)
Definition: pickup.c:246
#define ao2_unlink(container, obj)
Definition: astobj2.h:1598
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
struct stasis_message * stasis_message_create(struct stasis_message_type *type, void *data)
Create a new message.
int ast_channel_connected_line_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const void *connected_info, int frame)
Run a connected line interception subroutine and update a channel&#39;s connected line information...
Definition: channel.c:10539
struct ast_channel * ast_channel_masq(const struct ast_channel *chan)
Connected Line/Party information.
Definition: channel.h:457
#define ao2_iterator_next(iter)
Definition: astobj2.h:1933
struct timeval ast_channel_creationtime(struct ast_channel *chan)
#define LOG_NOTICE
Definition: logger.h:263
#define ast_channel_unlock(chan)
Definition: channel.h:2946
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic&#39;s subscribers.
Definition: stasis.c:1511
int ast_can_pickup(struct ast_channel *chan)
Test if a channel can be picked up.
Definition: pickup.c:77
int source
Information about the source of an update.
Definition: channel.h:483
#define ast_free(a)
Definition: astmm.h:182
int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *digits)
stream file until digit If the file name is non-empty, try to play it.
Definition: file.c:1814
static void to_ami(struct ast_sip_subscription *sub, struct ast_str **buf)
int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target)
Pickup a call target.
Definition: pickup.c:302
int ast_namedgroups_intersect(struct ast_namedgroups *a, struct ast_namedgroups *b)
Return TRUE if group a and b contain at least one common groupname.
Definition: channel.c:8192
int ast_pickup_call(struct ast_channel *chan)
Pickup a call.
Definition: pickup.c:200
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
static const struct ast_datastore_info pickup_active
Definition: pickup.c:73
void ast_party_connected_line_copy(struct ast_party_connected_line *dest, const struct ast_party_connected_line *src)
Copy the source connected line information to the destination connected line.
Definition: channel.c:2031
struct ast_channel_snapshot * ast_channel_snapshot_get_latest(const char *uniqueid)
Obtain the latest ast_channel_snapshot from the Stasis Message Bus API cache. This is an ao2 object...
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition: astobj2.h:1841
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
void ast_connected_line_copy_from_caller(struct ast_party_connected_line *dest, const struct ast_party_caller *src)
Copy the caller information to the connected line information.
Definition: channel.c:8389
struct ast_namedgroups * ast_channel_named_pickupgroups(const struct ast_channel *chan)
const char * ast_channel_name(const struct ast_channel *chan)
struct ast_channel_snapshot * ast_multi_channel_blob_get_channel(struct ast_multi_channel_blob *obj, const char *role)
Retrieve a channel snapshot associated with a specific role from a ast_multi_channel_blob.
A multi channel blob data structure for multi_channel_blob stasis messages.
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2814
Internal Asterisk hangup causes.
ast_group_t ast_channel_pickupgroup(const struct ast_channel *chan)
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:89
Generic container type.
#define ast_channel_trylock(chan)
Definition: channel.h:2947
ast_group_t ast_channel_callgroup(const struct ast_channel *chan)
struct ast_flags * ast_channel_flags(struct ast_channel *chan)
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2390
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
struct ast_party_id priv
Private connected party ID.
Definition: channel.h:469
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags) attribute_warn_unused_result
Create an iterator for a container.
int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore)
Remove a datastore from a channel.
Definition: channel.c:2399
struct ast_multi_channel_blob * ast_multi_channel_blob_create(struct ast_json *blob)
Create a ast_multi_channel_blob suitable for a stasis_message.
Configuration relating to call pickup.
#define ao2_link(container, obj)
Definition: astobj2.h:1549