Asterisk - The Open Source Telephony Project  21.4.1
func_periodic_hook.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2014, Russell Bryant
5  *
6  * Russell Bryant <russell@russellbryant.net>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*! \file
20  *
21  * \brief Periodic dialplan hooks.
22  *
23  * \author Russell Bryant <russell@russellbryant.net>
24  *
25  * \ingroup functions
26  */
27 
28 /*** MODULEINFO
29  <depend>app_chanspy</depend>
30  <depend>func_cut</depend>
31  <depend>func_groupcount</depend>
32  <depend>func_uri</depend>
33  <support_level>core</support_level>
34  ***/
35 
36 #include "asterisk.h"
37 
38 #include "asterisk/module.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/app.h"
42 #include "asterisk/audiohook.h"
43 #include "asterisk/test.h"
44 #define AST_API_MODULE
45 #include "asterisk/beep.h"
46 
47 /*** DOCUMENTATION
48  <function name="PERIODIC_HOOK" language="en_US">
49  <synopsis>
50  Execute a periodic dialplan hook into the audio of a call.
51  </synopsis>
52  <syntax>
53  <parameter name="context" required="true">
54  <para>(On Read Only) Context for the hook extension.</para>
55  </parameter>
56  <parameter name="extension" required="true">
57  <para>(On Read Only) The hook extension.</para>
58  </parameter>
59  <parameter name="interval" required="true">
60  <para>(On Read Only) Number of seconds in between hook runs.
61  Whole seconds only.</para>
62  </parameter>
63  <parameter name="hook_id" required="true">
64  <para>(On Write Only) The hook ID.</para>
65  </parameter>
66  </syntax>
67  <description>
68  <para>For example, you could use this function to enable playing
69  a periodic <literal>beep</literal> sound in a call.</para>
70  <para/>
71  <example title="To turn on">
72  same => n,Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})
73  </example>
74  <example title="To turn off">
75  same => n,Set(PERIODIC_HOOK(${BEEPID})=off)
76  </example>
77  <example title="To turn back on again later">
78  same => n,Set(PERIODIC_HOOK(${BEEPID})=on)
79  </example>
80  <para>It is important to note that the hook does not actually
81  run on the channel itself. It runs asynchronously on a new channel.
82  Any audio generated by the hook gets injected into the call for
83  the channel PERIODIC_HOOK() was set on.</para>
84  <para/>
85  <para>The hook dialplan will have two variables available.
86  <variable>HOOK_CHANNEL</variable> is the channel the hook is
87  enabled on. <variable>HOOK_ID</variable> is the hook ID for
88  enabling or disabling the hook.</para>
89  </description>
90  </function>
91  ***/
92 
93 static const char context_name[] = "__func_periodic_hook_context__";
94 static const char exten_name[] = "hook";
95 static const char full_exten_name[] = "hook@__func_periodic_hook_context__";
96 
97 static const char beep_exten[] = "beep";
98 
99 /*!
100  * \brief Last used hook ID
101  *
102  * This is incremented each time a hook is created to give each hook a unique
103  * ID.
104  */
105 static unsigned int global_hook_id;
106 
107 /*! State put in a datastore to track the state of the hook */
108 struct hook_state {
109  /*!
110  * \brief audiohook used as a callback into this module
111  *
112  * \note The code assumes this is the first element in the struct
113  */
115  /*! Seconds between each hook run */
116  unsigned int interval;
117  /*! The last time the hook ran */
118  struct timeval last_hook;
119  /*! Dialplan context for the hook */
120  char *context;
121  /*! Dialplan extension for the hook */
122  char *exten;
123  /*! Hook ID */
124  unsigned int hook_id;
125  /*! Non-zero if the hook is currently disabled */
126  unsigned char disabled;
127 };
128 
129 static void hook_datastore_destroy_callback(void *data)
130 {
131  struct hook_state *state = data;
132 
133  ast_audiohook_lock(&state->audiohook);
137 
138  ast_free(state->context);
139  ast_free(state->exten);
140  ast_free(state);
141 }
142 
143 static const struct ast_datastore_info hook_datastore = {
144  .type = AST_MODULE,
145  .destroy = hook_datastore_destroy_callback,
146 };
147 
148 /*! Arguments to the thread that launches the hook */
150  /*! Hook ID */
151  char *hook_id;
152  /*! Name of the channel the hook was set on */
153  char *chan_name;
154  /*! Dialplan context for the hook */
155  char *context;
156  /*! Dialplan extension for the hook */
157  char *exten;
158 };
159 
160 static void hook_thread_arg_destroy(struct hook_thread_arg *arg)
161 {
162  ast_free(arg->hook_id);
163  ast_free(arg->chan_name);
164  ast_free(arg->context);
165  ast_free(arg->exten);
166  ast_free(arg);
167 }
168 
169 static void *hook_launch_thread(void *data)
170 {
171  struct hook_thread_arg *arg = data;
172  struct ast_variable hook_id = {
173  .name = "HOOK_ID",
174  .value = arg->hook_id,
175  };
176  struct ast_variable chan_name_var = {
177  .name = "HOOK_CHANNEL",
178  .value = arg->chan_name,
179  .next = &hook_id,
180  };
181 
182  ast_pbx_outgoing_exten("Local", NULL, full_exten_name, 60,
183  arg->context, arg->exten, 1, NULL, AST_OUTGOING_NO_WAIT,
184  NULL, NULL, &chan_name_var, NULL, NULL, 1, NULL);
185 
186  hook_thread_arg_destroy(arg);
187 
188  return NULL;
189 }
190 
191 static struct hook_thread_arg *hook_thread_arg_alloc(struct ast_channel *chan,
192  struct hook_state *state)
193 {
194  struct hook_thread_arg *arg;
195 
196  if (!(arg = ast_calloc(1, sizeof(*arg)))) {
197  return NULL;
198  }
199 
200  ast_channel_lock(chan);
201  arg->chan_name = ast_strdup(ast_channel_name(chan));
202  ast_channel_unlock(chan);
203  if (!arg->chan_name) {
204  hook_thread_arg_destroy(arg);
205  return NULL;
206  }
207 
208  if (ast_asprintf(&arg->hook_id, "%u", state->hook_id) == -1) {
209  hook_thread_arg_destroy(arg);
210  return NULL;
211  }
212 
213  if (!(arg->context = ast_strdup(state->context))) {
214  hook_thread_arg_destroy(arg);
215  return NULL;
216  }
217 
218  if (!(arg->exten = ast_strdup(state->exten))) {
219  hook_thread_arg_destroy(arg);
220  return NULL;
221  }
222 
223  return arg;
224 }
225 
226 static int do_hook(struct ast_channel *chan, struct hook_state *state)
227 {
228  pthread_t t;
229  struct hook_thread_arg *arg;
230  int res;
231 
232  if (!(arg = hook_thread_arg_alloc(chan, state))) {
233  return -1;
234  }
235 
236  /*
237  * We don't want to block normal frame processing *at all* while we kick
238  * this off, so do it in a new thread.
239  */
240  res = ast_pthread_create_detached_background(&t, NULL, hook_launch_thread, arg);
241  if (res != 0) {
242  hook_thread_arg_destroy(arg);
243  }
244 
245  return res;
246 }
247 
248 static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan,
249  struct ast_frame *frame, enum ast_audiohook_direction direction)
250 {
251  struct hook_state *state = (struct hook_state *) audiohook; /* trust me. */
252  struct timeval now;
253  int res = 0;
254 
255  if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || state->disabled) {
256  return 0;
257  }
258 
259  now = ast_tvnow();
260  if (ast_tvdiff_ms(now, state->last_hook) > state->interval * 1000) {
261  if ((res = do_hook(chan, state))) {
262  const char *name;
263  ast_channel_lock(chan);
264  name = ast_strdupa(ast_channel_name(chan));
265  ast_channel_unlock(chan);
266  ast_log(LOG_WARNING, "Failed to run hook on '%s'\n", name);
267  }
268  state->last_hook = now;
269  }
270 
271  return res;
272 }
273 
274 static struct hook_state *hook_state_alloc(const char *context, const char *exten,
275  unsigned int interval, unsigned int hook_id)
276 {
277  struct hook_state *state;
278 
279  if (!(state = ast_calloc(1, sizeof(*state)))) {
280  return NULL;
281  }
282 
283  state->context = ast_strdup(context);
284  state->exten = ast_strdup(exten);
285  state->interval = interval;
286  state->hook_id = hook_id;
287 
290  state->audiohook.manipulate_callback = hook_callback;
291 
292  return state;
293 }
294 
295 static int init_hook(struct ast_channel *chan, const char *context, const char *exten,
296  unsigned int interval, unsigned int hook_id)
297 {
298  struct hook_state *state;
299  struct ast_datastore *datastore;
300  char uid[32];
301 
302  snprintf(uid, sizeof(uid), "%u", hook_id);
303 
304  if (!(datastore = ast_datastore_alloc(&hook_datastore, uid))) {
305  return -1;
306  }
307 
308  if (!(state = hook_state_alloc(context, exten, interval, hook_id))) {
309  ast_datastore_free(datastore);
310  return -1;
311  }
312  datastore->data = state;
313 
314  ast_channel_lock(chan);
315  ast_channel_datastore_add(chan, datastore);
316  ast_audiohook_attach(chan, &state->audiohook);
317  ast_channel_unlock(chan);
318 
319  return 0;
320 }
321 
322 static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
323 {
324  char *parse = ast_strdupa(S_OR(data, ""));
326  AST_APP_ARG(context);
327  AST_APP_ARG(exten);
328  AST_APP_ARG(interval);
329  );
330  unsigned int interval;
331 
332  AST_STANDARD_APP_ARGS(args, parse);
333 
334  if (ast_strlen_zero(args.interval) ||
335  sscanf(args.interval, "%30u", &interval) != 1 || interval == 0) {
336  ast_log(LOG_WARNING, "Invalid hook interval: '%s'\n", S_OR(args.interval, ""));
337  return -1;
338  }
339 
340  if (ast_strlen_zero(args.context) || ast_strlen_zero(args.exten)) {
341  ast_log(LOG_WARNING, "A context and extension are required for PERIODIC_HOOK().\n");
342  return -1;
343  }
344 
345  ast_debug(1, "hook to %s@%s enabled on %s with interval of %u seconds\n",
346  args.exten, args.context, ast_channel_name(chan), interval);
347  ast_test_suite_event_notify("PERIODIC_HOOK_ENABLED", "Exten: %s\r\nChannel: %s\r\nInterval: %u\r\n",
348  args.exten, ast_channel_name(chan), interval);
349 
350  return init_hook(chan, args.context, args.exten, interval, hook_id);
351 }
352 
353 static int hook_off(struct ast_channel *chan, const char *hook_id)
354 {
355  struct ast_datastore *datastore;
356  struct hook_state *state;
357 
358  if (ast_strlen_zero(hook_id)) {
359  return -1;
360  }
361 
362  ast_channel_lock(chan);
363 
364  if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, hook_id))) {
365  ast_log(LOG_WARNING, "Hook with ID '%s' not found on channel '%s'\n", hook_id,
366  ast_channel_name(chan));
367  ast_channel_unlock(chan);
368  return -1;
369  }
370 
371  state = datastore->data;
372  state->disabled = 1;
373 
374  ast_channel_unlock(chan);
375 
376  return 0;
377 }
378 
379 static int hook_read(struct ast_channel *chan, const char *cmd, char *data,
380  char *buf, size_t len)
381 {
382  unsigned int hook_id;
383 
384  if (!chan) {
385  return -1;
386  }
387 
388  hook_id = (unsigned int) ast_atomic_fetchadd_int((int *) &global_hook_id, 1);
389 
390  snprintf(buf, len, "%u", hook_id);
391 
392  return hook_on(chan, data, hook_id);
393 }
394 
395 static int hook_re_enable(struct ast_channel *chan, const char *uid)
396 {
397  struct ast_datastore *datastore;
398  struct hook_state *state;
399 
400  if (ast_strlen_zero(uid)) {
401  return -1;
402  }
403 
404  ast_channel_lock(chan);
405 
406  if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, uid))) {
407  ast_log(LOG_WARNING, "Hook with ID '%s' not found on '%s'\n",
408  uid, ast_channel_name(chan));
409  ast_channel_unlock(chan);
410  return -1;
411  }
412 
413  state = datastore->data;
414  state->disabled = 0;
415 
416  ast_channel_unlock(chan);
417 
418  return 0;
419 }
420 
421 static int hook_write(struct ast_channel *chan, const char *cmd, char *data,
422  const char *value)
423 {
424  int res;
425 
426  if (!chan) {
427  return -1;
428  }
429 
430  if (ast_false(value)) {
431  res = hook_off(chan, data);
432  } else if (ast_true(value)) {
433  res = hook_re_enable(chan, data);
434  } else {
435  ast_log(LOG_WARNING, "Invalid value for PERIODIC_HOOK function: '%s'\n", value);
436  res = -1;
437  }
438 
439  return res;
440 }
441 
442 static struct ast_custom_function hook_function = {
443  .name = "PERIODIC_HOOK",
444  .read = hook_read,
445  .write = hook_write,
446 };
447 
448 static int unload_module(void)
449 {
450  ast_context_destroy(NULL, AST_MODULE);
451 
452  ast_custom_function_unregister(&hook_function);
453  return 0;
454 }
455 
456 static int load_module(void)
457 {
458  int res;
459 
460  if (!ast_context_find_or_create(NULL, NULL, context_name, AST_MODULE)) {
461  ast_log(LOG_ERROR, "Failed to create %s dialplan context.\n", context_name);
463  }
464 
465  /*
466  * Based on a handy recipe from the Asterisk Cookbook.
467  */
468  res = ast_add_extension(context_name, 1, exten_name, 1, "", "",
469  "Set", "EncodedChannel=${HOOK_CHANNEL}",
470  NULL, AST_MODULE);
471  res |= ast_add_extension(context_name, 1, exten_name, 2, "", "",
472  "Set", "GROUP_NAME=${EncodedChannel}${HOOK_ID}",
473  NULL, AST_MODULE);
474  res |= ast_add_extension(context_name, 1, exten_name, 3, "", "",
475  "Set", "GROUP(periodic-hook)=${GROUP_NAME}",
476  NULL, AST_MODULE);
477  res |= ast_add_extension(context_name, 1, exten_name, 4, "", "", "ExecIf",
478  "$[${GROUP_COUNT(${GROUP_NAME}@periodic-hook)} > 1]?Hangup()",
479  NULL, AST_MODULE);
480  res |= ast_add_extension(context_name, 1, exten_name, 5, "", "",
481  "Set", "ChannelToSpy=${URIDECODE(${EncodedChannel})}",
482  NULL, AST_MODULE);
483  res |= ast_add_extension(context_name, 1, exten_name, 6, "", "",
484  "ChanSpy", "${ChannelToSpy},qEB", NULL, AST_MODULE);
485 
486  res |= ast_add_extension(context_name, 1, beep_exten, 1, "", "",
487  "Answer", "", NULL, AST_MODULE);
488  res |= ast_add_extension(context_name, 1, beep_exten, 2, "", "",
489  "Playback", "beep", NULL, AST_MODULE);
490  res |= ast_add_extension(context_name, 1, beep_exten, 3, "", "",
491  "Hangup", "", NULL, AST_MODULE);
492 
493  res |= ast_custom_function_register_escalating(&hook_function, AST_CFE_BOTH);
494 
495  if (res) {
496  unload_module();
498  }
500 }
501 
502 int AST_OPTIONAL_API_NAME(ast_beep_start)(struct ast_channel *chan,
503  unsigned int interval, char *beep_id, size_t len)
504 {
505  char args[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 32];
506 
507  snprintf(args, sizeof(args), "%s,%s,%u",
508  context_name, beep_exten, interval);
509 
510  if (hook_read(chan, NULL, args, beep_id, len)) {
511  ast_log(LOG_WARNING, "Failed to enable periodic beep.\n");
512  return -1;
513  }
514 
515  return 0;
516 }
517 
518 int AST_OPTIONAL_API_NAME(ast_beep_stop)(struct ast_channel *chan, const char *beep_id)
519 {
520  return hook_write(chan, NULL, (char *) beep_id, "off");
521 }
522 
523 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Periodic dialplan hooks.",
524  .support_level = AST_MODULE_SUPPORT_CORE,
525  .load = load_module,
526  .unload = unload_module,
527  .requires = "app_chanspy,func_cut,func_groupcount,func_uri",
528 );
struct ast_audiohook audiohook
audiohook used as a callback into this module
const char * name
Definition: pbx.h:119
const char * type
Definition: datastore.h:32
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
unsigned int hook_id
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
Audiohooks Architecture.
Structure for variables, used for configurations and for channel variables.
Test Framework API.
Structure for a data store type.
Definition: datastore.h:31
int ast_audiohook_attach(struct ast_channel *chan, struct ast_audiohook *audiohook)
Attach audiohook to channel.
Definition: audiohook.c:484
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Structure for a data store object.
Definition: datastore.h:64
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:2399
int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *context, const char *exten, int priority, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel, int early_media, const struct ast_assigned_ids *assignedids)
Synchronously or asynchronously make an outbound call and send it to a particular extension...
Definition: pbx.c:7916
int ast_audiohook_destroy(struct ast_audiohook *audiohook)
Destroys an audiohook structure.
Definition: audiohook.c:124
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
Periodic beeps into the audio of a call.
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
const char * uid
Definition: datastore.h:65
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
ast_audiohook_manipulate_callback manipulate_callback
Definition: audiohook.h:118
int ast_audiohook_init(struct ast_audiohook *audiohook, enum ast_audiohook_type type, const char *source, enum ast_audiohook_init_flags flags)
Initialize an audiohook structure.
Definition: audiohook.c:100
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
#define ast_audiohook_unlock(ah)
Unlock an audiohook.
Definition: audiohook.h:318
unsigned int interval
unsigned char disabled
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define AST_MAX_EXTENSION
Definition: channel.h:134
#define ast_debug(level,...)
Log a DEBUG message.
static unsigned int global_hook_id
Last used hook ID.
Core PBX routines and definitions.
#define ast_test_suite_event_notify(s, f,...)
Definition: test.h:189
struct timeval last_hook
int ast_audiohook_detach(struct ast_audiohook *audiohook)
Detach audiohook from channel.
Definition: audiohook.c:550
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: utils.c:2199
#define AST_MAX_CONTEXT
Definition: channel.h:135
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
ast_audiohook_direction
Definition: audiohook.h:48
void * data
Definition: datastore.h:66
int ast_add_extension(const char *context, int replace, const char *extension, int priority, const char *label, const char *callerid, const char *application, void *data, void(*datad)(void *), const char *registrar)
Add and extension to an extension context.
Definition: pbx.c:6928
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"...
Definition: utils.c:2216
Data structure associated with a single frame of data.
void ast_context_destroy(struct ast_context *con, const char *registrar)
Destroy a context (matches the specified context or ANY context if NULL)
Definition: pbx.c:8221
enum ast_audiohook_status status
Definition: audiohook.h:108
#define AST_OPTIONAL_API_NAME(name)
Expands to the name of the implementation function.
Definition: optional_api.h:228
struct ast_context * ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
Register a new context or find an existing one.
Definition: pbx.c:6149
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
#define ast_audiohook_lock(ah)
Lock an audiohook.
Definition: audiohook.h:313
Asterisk module definitions.
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define AST_APP_ARG(name)
Define an application argument.