Asterisk - The Open Source Telephony Project  GIT-master-b7027de
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 #define AST_API_MODULE
44 #include "asterisk/beep.h"
45 
46 /*** DOCUMENTATION
47  <function name="PERIODIC_HOOK" language="en_US">
48  <synopsis>
49  Execute a periodic dialplan hook into the audio of a call.
50  </synopsis>
51  <syntax>
52  <parameter name="context" required="true">
53  <para>(On Read Only) Context for the hook extension.</para>
54  </parameter>
55  <parameter name="extension" required="true">
56  <para>(On Read Only) The hook extension.</para>
57  </parameter>
58  <parameter name="interval" required="true">
59  <para>(On Read Only) Number of seconds in between hook runs.
60  Whole seconds only.</para>
61  </parameter>
62  <parameter name="hook_id" required="true">
63  <para>(On Write Only) The hook ID.</para>
64  </parameter>
65  </syntax>
66  <description>
67  <para>For example, you could use this function to enable playing
68  a periodic <literal>beep</literal> sound in a call.</para>
69  <para/>
70  <para>To turn on:</para>
71  <para> Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})</para>
72  <para/>
73  <para>To turn off:</para>
74  <para> Set(PERIODIC_HOOK(${BEEPID})=off)</para>
75  <para/>
76  <para>To turn back on again later:</para>
77  <para>Set(PERIODIC_HOOK(${BEEPID})=on)</para>
78  <para/>
79  <para>It is important to note that the hook does not actually
80  run on the channel itself. It runs asynchronously on a new channel.
81  Any audio generated by the hook gets injected into the call for
82  the channel PERIODIC_HOOK() was set on.</para>
83  <para/>
84  <para>The hook dialplan will have two variables available.
85  <variable>HOOK_CHANNEL</variable> is the channel the hook is
86  enabled on. <variable>HOOK_ID</variable> is the hook ID for
87  enabling or disabling the hook.</para>
88  </description>
89  </function>
90  ***/
91 
92 static const char context_name[] = "__func_periodic_hook_context__";
93 static const char exten_name[] = "hook";
94 static const char full_exten_name[] = "hook@__func_periodic_hook_context__";
95 
96 static const char beep_exten[] = "beep";
97 
98 /*!
99  * \brief Last used hook ID
100  *
101  * This is incremented each time a hook is created to give each hook a unique
102  * ID.
103  */
104 static unsigned int global_hook_id;
105 
106 /*! State put in a datastore to track the state of the hook */
107 struct hook_state {
108  /*!
109  * \brief audiohook used as a callback into this module
110  *
111  * \note The code assumes this is the first element in the struct
112  */
114  /*! Seconds between each hook run */
115  unsigned int interval;
116  /*! The last time the hook ran */
117  struct timeval last_hook;
118  /*! Dialplan context for the hook */
119  char *context;
120  /*! Dialplan extension for the hook */
121  char *exten;
122  /*! Hook ID */
123  unsigned int hook_id;
124  /*! Non-zero if the hook is currently disabled */
125  unsigned char disabled;
126 };
127 
128 static void hook_datastore_destroy_callback(void *data)
129 {
130  struct hook_state *state = data;
131 
132  ast_audiohook_lock(&state->audiohook);
136 
137  ast_free(state->context);
138  ast_free(state->exten);
139  ast_free(state);
140 }
141 
142 static const struct ast_datastore_info hook_datastore = {
143  .type = AST_MODULE,
145 };
146 
147 /*! Arguments to the thread that launches the hook */
149  /*! Hook ID */
150  char *hook_id;
151  /*! Name of the channel the hook was set on */
152  char *chan_name;
153  /*! Dialplan context for the hook */
154  char *context;
155  /*! Dialplan extension for the hook */
156  char *exten;
157 };
158 
159 static void hook_thread_arg_destroy(struct hook_thread_arg *arg)
160 {
161  ast_free(arg->hook_id);
162  ast_free(arg->chan_name);
163  ast_free(arg->context);
164  ast_free(arg->exten);
165  ast_free(arg);
166 }
167 
168 static void *hook_launch_thread(void *data)
169 {
170  struct hook_thread_arg *arg = data;
171  struct ast_variable hook_id = {
172  .name = "HOOK_ID",
173  .value = arg->hook_id,
174  };
175  struct ast_variable chan_name_var = {
176  .name = "HOOK_CHANNEL",
177  .value = arg->chan_name,
178  .next = &hook_id,
179  };
180 
182  arg->context, arg->exten, 1, NULL, AST_OUTGOING_NO_WAIT,
183  NULL, NULL, &chan_name_var, NULL, NULL, 1, NULL);
184 
186 
187  return NULL;
188 }
189 
191  struct hook_state *state)
192 {
193  struct hook_thread_arg *arg;
194 
195  if (!(arg = ast_calloc(1, sizeof(*arg)))) {
196  return NULL;
197  }
198 
199  ast_channel_lock(chan);
200  arg->chan_name = ast_strdup(ast_channel_name(chan));
201  ast_channel_unlock(chan);
202  if (!arg->chan_name) {
204  return NULL;
205  }
206 
207  if (ast_asprintf(&arg->hook_id, "%u", state->hook_id) == -1) {
209  return NULL;
210  }
211 
212  if (!(arg->context = ast_strdup(state->context))) {
214  return NULL;
215  }
216 
217  if (!(arg->exten = ast_strdup(state->exten))) {
219  return NULL;
220  }
221 
222  return arg;
223 }
224 
225 static int do_hook(struct ast_channel *chan, struct hook_state *state)
226 {
227  pthread_t t;
228  struct hook_thread_arg *arg;
229  int res;
230 
231  if (!(arg = hook_thread_arg_alloc(chan, state))) {
232  return -1;
233  }
234 
235  /*
236  * We don't want to block normal frame processing *at all* while we kick
237  * this off, so do it in a new thread.
238  */
240  if (res != 0) {
242  }
243 
244  return res;
245 }
246 
247 static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan,
248  struct ast_frame *frame, enum ast_audiohook_direction direction)
249 {
250  struct hook_state *state = (struct hook_state *) audiohook; /* trust me. */
251  struct timeval now;
252  int res = 0;
253 
254  if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || state->disabled) {
255  return 0;
256  }
257 
258  now = ast_tvnow();
259  if (ast_tvdiff_ms(now, state->last_hook) > state->interval * 1000) {
260  if ((res = do_hook(chan, state))) {
261  const char *name;
262  ast_channel_lock(chan);
263  name = ast_strdupa(ast_channel_name(chan));
264  ast_channel_unlock(chan);
265  ast_log(LOG_WARNING, "Failed to run hook on '%s'\n", name);
266  }
267  state->last_hook = now;
268  }
269 
270  return res;
271 }
272 
273 static struct hook_state *hook_state_alloc(const char *context, const char *exten,
274  unsigned int interval, unsigned int hook_id)
275 {
276  struct hook_state *state;
277 
278  if (!(state = ast_calloc(1, sizeof(*state)))) {
279  return NULL;
280  }
281 
282  state->context = ast_strdup(context);
283  state->exten = ast_strdup(exten);
284  state->interval = interval;
285  state->hook_id = hook_id;
286 
290 
291  return state;
292 }
293 
294 static int init_hook(struct ast_channel *chan, const char *context, const char *exten,
295  unsigned int interval, unsigned int hook_id)
296 {
297  struct hook_state *state;
298  struct ast_datastore *datastore;
299  char uid[32];
300 
301  snprintf(uid, sizeof(uid), "%u", hook_id);
302 
303  if (!(datastore = ast_datastore_alloc(&hook_datastore, uid))) {
304  return -1;
305  }
306 
307  if (!(state = hook_state_alloc(context, exten, interval, hook_id))) {
308  ast_datastore_free(datastore);
309  return -1;
310  }
311  datastore->data = state;
312 
313  ast_channel_lock(chan);
314  ast_channel_datastore_add(chan, datastore);
315  ast_audiohook_attach(chan, &state->audiohook);
316  ast_channel_unlock(chan);
317 
318  return 0;
319 }
320 
321 static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
322 {
323  char *parse = ast_strdupa(S_OR(data, ""));
328  );
329  unsigned int interval;
330 
331  AST_STANDARD_APP_ARGS(args, parse);
332 
333  if (ast_strlen_zero(args.interval) ||
334  sscanf(args.interval, "%30u", &interval) != 1 || interval == 0) {
335  ast_log(LOG_WARNING, "Invalid hook interval: '%s'\n", S_OR(args.interval, ""));
336  return -1;
337  }
338 
339  if (ast_strlen_zero(args.context) || ast_strlen_zero(args.exten)) {
340  ast_log(LOG_WARNING, "A context and extension are required for PERIODIC_HOOK().\n");
341  return -1;
342  }
343 
344  ast_debug(1, "hook to %s@%s enabled on %s with interval of %u seconds\n",
345  args.exten, args.context, ast_channel_name(chan), interval);
346 
347  return init_hook(chan, args.context, args.exten, interval, hook_id);
348 }
349 
350 static int hook_off(struct ast_channel *chan, const char *hook_id)
351 {
352  struct ast_datastore *datastore;
353  struct hook_state *state;
354 
355  if (ast_strlen_zero(hook_id)) {
356  return -1;
357  }
358 
359  ast_channel_lock(chan);
360 
361  if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, hook_id))) {
362  ast_log(LOG_WARNING, "Hook with ID '%s' not found on channel '%s'\n", hook_id,
363  ast_channel_name(chan));
364  ast_channel_unlock(chan);
365  return -1;
366  }
367 
368  state = datastore->data;
369  state->disabled = 1;
370 
371  ast_channel_unlock(chan);
372 
373  return 0;
374 }
375 
376 static int hook_read(struct ast_channel *chan, const char *cmd, char *data,
377  char *buf, size_t len)
378 {
379  unsigned int hook_id;
380 
381  if (!chan) {
382  return -1;
383  }
384 
385  hook_id = (unsigned int) ast_atomic_fetchadd_int((int *) &global_hook_id, 1);
386 
387  snprintf(buf, len, "%u", hook_id);
388 
389  return hook_on(chan, data, hook_id);
390 }
391 
392 static int hook_re_enable(struct ast_channel *chan, const char *uid)
393 {
394  struct ast_datastore *datastore;
395  struct hook_state *state;
396 
397  if (ast_strlen_zero(uid)) {
398  return -1;
399  }
400 
401  ast_channel_lock(chan);
402 
403  if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, uid))) {
404  ast_log(LOG_WARNING, "Hook with ID '%s' not found on '%s'\n",
405  uid, ast_channel_name(chan));
406  ast_channel_unlock(chan);
407  return -1;
408  }
409 
410  state = datastore->data;
411  state->disabled = 0;
412 
413  ast_channel_unlock(chan);
414 
415  return 0;
416 }
417 
418 static int hook_write(struct ast_channel *chan, const char *cmd, char *data,
419  const char *value)
420 {
421  int res;
422 
423  if (!chan) {
424  return -1;
425  }
426 
427  if (ast_false(value)) {
428  res = hook_off(chan, data);
429  } else if (ast_true(value)) {
430  res = hook_re_enable(chan, data);
431  } else {
432  ast_log(LOG_WARNING, "Invalid value for PERIODIC_HOOK function: '%s'\n", value);
433  res = -1;
434  }
435 
436  return res;
437 }
438 
440  .name = "PERIODIC_HOOK",
441  .read = hook_read,
442  .write = hook_write,
443 };
444 
445 static int unload_module(void)
446 {
448 
449  ast_custom_function_unregister(&hook_function);
450  return 0;
451 }
452 
453 static int load_module(void)
454 {
455  int res;
456 
458  ast_log(LOG_ERROR, "Failed to create %s dialplan context.\n", context_name);
460  }
461 
462  /*
463  * Based on a handy recipe from the Asterisk Cookbook.
464  */
465  res = ast_add_extension(context_name, 1, exten_name, 1, "", "",
466  "Set", "EncodedChannel=${CUT(HOOK_CHANNEL,-,1-2)}",
467  NULL, AST_MODULE);
468  res |= ast_add_extension(context_name, 1, exten_name, 2, "", "",
469  "Set", "GROUP_NAME=${EncodedChannel}${HOOK_ID}",
470  NULL, AST_MODULE);
471  res |= ast_add_extension(context_name, 1, exten_name, 3, "", "",
472  "Set", "GROUP(periodic-hook)=${GROUP_NAME}",
473  NULL, AST_MODULE);
474  res |= ast_add_extension(context_name, 1, exten_name, 4, "", "", "ExecIf",
475  "$[${GROUP_COUNT(${GROUP_NAME}@periodic-hook)} > 1]?Hangup()",
476  NULL, AST_MODULE);
477  res |= ast_add_extension(context_name, 1, exten_name, 5, "", "",
478  "Set", "ChannelToSpy=${URIDECODE(${EncodedChannel})}",
479  NULL, AST_MODULE);
480  res |= ast_add_extension(context_name, 1, exten_name, 6, "", "",
481  "ChanSpy", "${ChannelToSpy},qEB", NULL, AST_MODULE);
482 
483  res |= ast_add_extension(context_name, 1, beep_exten, 1, "", "",
484  "Answer", "", NULL, AST_MODULE);
485  res |= ast_add_extension(context_name, 1, beep_exten, 2, "", "",
486  "Playback", "beep", NULL, AST_MODULE);
487 
489 
490  if (res) {
491  unload_module();
493  }
495 }
496 
498  unsigned int interval, char *beep_id, size_t len)
499 {
501 
502  snprintf(args, sizeof(args), "%s,%s,%u",
504 
505  if (hook_read(chan, NULL, args, beep_id, len)) {
506  ast_log(LOG_WARNING, "Failed to enable periodic beep.\n");
507  return -1;
508  }
509 
510  return 0;
511 }
512 
513 int AST_OPTIONAL_API_NAME(ast_beep_stop)(struct ast_channel *chan, const char *beep_id)
514 {
515  return hook_write(chan, NULL, (char *) beep_id, "off");
516 }
517 
518 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Periodic dialplan hooks.",
519  .support_level = AST_MODULE_SUPPORT_CORE,
520  .load = load_module,
521  .unload = unload_module,
522  .requires = "app_chanspy,func_cut,func_groupcount,func_uri",
523 );
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
enum sip_cc_notify_state state
Definition: chan_sip.c:960
#define ast_channel_lock(chan)
Definition: channel.h:2902
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
static struct hook_state * hook_state_alloc(const char *context, const char *exten, unsigned int interval, unsigned int hook_id)
static int hook_off(struct ast_channel *chan, const char *hook_id)
unsigned int hook_id
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
#define LOG_WARNING
Definition: logger.h:274
Audiohooks Architecture.
static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
Structure for variables, used for configurations and for channel variables.
static void hook_thread_arg_destroy(struct hook_thread_arg *arg)
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:501
int AST_OPTIONAL_API_NAME() ast_beep_stop(struct ast_channel *chan, const char *beep_id)
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:150
static int hook_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:98
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
Structure for a data store object.
Definition: datastore.h:68
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:2390
const char * args
#define NULL
Definition: resample.c:96
int value
Definition: syslog.c:37
static int do_hook(struct ast_channel *chan, struct hook_state *state)
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:7951
int ast_audiohook_destroy(struct ast_audiohook *audiohook)
Destroys an audiohook structure.
Definition: audiohook.c:133
#define ast_pthread_create_detached_background(a, b, c, d)
Definition: utils.h:572
static struct hook_thread_arg * hook_thread_arg_alloc(struct ast_channel *chan, struct hook_state *state)
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:755
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:69
static const char context_name[]
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:269
ast_audiohook_manipulate_callback manipulate_callback
Definition: audiohook.h:117
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:108
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1517
#define ast_audiohook_unlock(ah)
Unlock an audiohook.
Definition: audiohook.h:300
unsigned int interval
unsigned char disabled
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:444
#define ast_log
Definition: astobj2.c:42
static int hook_re_enable(struct ast_channel *chan, const char *uid)
General Asterisk PBX channel definitions.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define AST_MAX_EXTENSION
Definition: channel.h:135
static void hook_datastore_destroy_callback(void *data)
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
static const char beep_exten[]
static unsigned int global_hook_id
Last used hook ID.
Core PBX routines and definitions.
int AST_OPTIONAL_API_NAME() ast_beep_start(struct ast_channel *chan, unsigned int interval, char *beep_id, size_t len)
struct timeval last_hook
int ast_audiohook_detach(struct ast_audiohook *audiohook)
Detach audiohook from channel.
Definition: audiohook.c:579
#define LOG_ERROR
Definition: logger.h:285
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: main/utils.c:1951
static int unload_module(void)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define ast_strlen_zero(a)
Definition: muted.c:73
#define ast_channel_unlock(chan)
Definition: channel.h:2903
static void parse(struct mgcp_request *req)
Definition: chan_mgcp.c:1872
#define AST_MAX_CONTEXT
Definition: channel.h:136
static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
static const char name[]
Definition: cdr_mysql.c:74
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static int load_module(void)
ast_audiohook_direction
Definition: audiohook.h:48
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
void * data
Definition: datastore.h:70
static int hook_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
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:6970
static const char full_exten_name[]
#define AST_MODULE
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:79
const char * ast_channel_name(const struct ast_channel *chan)
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"...
Definition: main/utils.c:1968
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: conf2ael.c:625
enum ast_audiohook_status status
Definition: audiohook.h:107
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:89
#define AST_OPTIONAL_API_NAME(name)
Expands to the name of the implementation function.
Definition: optional_api.h:228
static void * hook_launch_thread(void *data)
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:6198
static const char exten_name[]
#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:295
Asterisk module definitions.
static const struct ast_datastore_info hook_datastore
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2376
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
static int init_hook(struct ast_channel *chan, const char *context, const char *exten, unsigned int interval, unsigned int hook_id)
static struct ast_custom_function hook_function
#define AST_APP_ARG(name)
Define an application argument.