Asterisk - The Open Source Telephony Project  18.5.0
func_realtime.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005-2006, BJ Weschke. All rights reserved.
5  *
6  * BJ Weschke <[email protected]>
7  *
8  * This code is released by the author with no restrictions on usage.
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  */
17 
18 /*! \file
19  *
20  * \brief REALTIME dialplan function
21  *
22  * \author BJ Weschke <[email protected]>
23  *
24  * \ingroup functions
25  */
26 
27 /*** MODULEINFO
28  <support_level>core</support_level>
29  ***/
30 
31 #include "asterisk.h"
32 
33 #include "asterisk/file.h"
34 #include "asterisk/channel.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/config.h"
37 #include "asterisk/module.h"
38 #include "asterisk/lock.h"
39 #include "asterisk/utils.h"
40 #include "asterisk/app.h"
41 
42 /*** DOCUMENTATION
43  <function name="REALTIME" language="en_US">
44  <synopsis>
45  RealTime Read/Write Functions.
46  </synopsis>
47  <syntax>
48  <parameter name="family" required="true" />
49  <parameter name="fieldmatch" required="true" />
50  <parameter name="matchvalue" />
51  <parameter name="delim1|field">
52  <para>Use <replaceable>delim1</replaceable> with <replaceable>delim2</replaceable> on
53  read and <replaceable>field</replaceable> without <replaceable>delim2</replaceable> on
54  write</para>
55  <para>If we are reading and <replaceable>delim1</replaceable> is not specified, defaults
56  to <literal>,</literal></para>
57  </parameter>
58  <parameter name="delim2">
59  <para>Parameter only used when reading, if not specified defaults to <literal>=</literal></para>
60  </parameter>
61  </syntax>
62  <description>
63  <para>This function will read or write values from/to a RealTime repository.
64  REALTIME(....) will read names/values from the repository, and
65  REALTIME(....)= will write a new value/field to the repository. On a
66  read, this function returns a delimited text string. The name/value
67  pairs are delimited by <replaceable>delim1</replaceable>, and the name and value are delimited
68  between each other with delim2.
69  If there is no match, NULL will be returned by the function.
70  On a write, this function will always return NULL.</para>
71  </description>
72  <see-also>
73  <ref type="function">REALTIME_STORE</ref>
74  <ref type="function">REALTIME_DESTROY</ref>
75  <ref type="function">REALTIME_FIELD</ref>
76  <ref type="function">REALTIME_HASH</ref>
77  </see-also>
78  </function>
79  <function name="REALTIME_STORE" language="en_US">
80  <synopsis>
81  RealTime Store Function.
82  </synopsis>
83  <syntax>
84  <parameter name="family" required="true" />
85  <parameter name="field1" required="true" />
86  <parameter name="fieldN" required="true" multiple="true" />
87  <parameter name="field30" required="true" />
88  </syntax>
89  <description>
90  <para>This function will insert a new set of values into the RealTime repository.
91  If RT engine provides an unique ID of the stored record, REALTIME_STORE(...)=..
92  creates channel variable named RTSTOREID, which contains value of unique ID.
93  Currently, a maximum of 30 field/value pairs is supported.</para>
94  </description>
95  <see-also>
96  <ref type="function">REALTIME</ref>
97  <ref type="function">REALTIME_DESTROY</ref>
98  <ref type="function">REALTIME_FIELD</ref>
99  <ref type="function">REALTIME_HASH</ref>
100  </see-also>
101  </function>
102  <function name="REALTIME_DESTROY" language="en_US">
103  <synopsis>
104  RealTime Destroy Function.
105  </synopsis>
106  <syntax>
107  <parameter name="family" required="true" />
108  <parameter name="fieldmatch" required="true" />
109  <parameter name="matchvalue" />
110  <parameter name="delim1" />
111  <parameter name="delim2" />
112  </syntax>
113  <description>
114  <para>This function acts in the same way as REALTIME(....) does, except that
115  it destroys the matched record in the RT engine.</para>
116  <note>
117  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
118  is set to <literal>no</literal>, this function can only be read from the
119  dialplan, and not directly from external protocols. It can, however, be
120  executed as a write operation (<literal>REALTIME_DESTROY(family, fieldmatch)=ignored</literal>)</para>
121  </note>
122  </description>
123  <see-also>
124  <ref type="function">REALTIME</ref>
125  <ref type="function">REALTIME_STORE</ref>
126  <ref type="function">REALTIME_FIELD</ref>
127  <ref type="function">REALTIME_HASH</ref>
128  </see-also>
129  </function>
130  <function name="REALTIME_FIELD" language="en_US">
131  <synopsis>
132  RealTime query function.
133  </synopsis>
134  <syntax>
135  <parameter name="family" required="true" />
136  <parameter name="fieldmatch" required="true" />
137  <parameter name="matchvalue" required="true" />
138  <parameter name="fieldname" required="true" />
139  </syntax>
140  <description>
141  <para>This function retrieves a single item, <replaceable>fieldname</replaceable>
142  from the RT engine, where <replaceable>fieldmatch</replaceable> contains the value
143  <replaceable>matchvalue</replaceable>. When written to, the REALTIME_FIELD() function
144  performs identically to the REALTIME() function.</para>
145  </description>
146  <see-also>
147  <ref type="function">REALTIME</ref>
148  <ref type="function">REALTIME_STORE</ref>
149  <ref type="function">REALTIME_DESTROY</ref>
150  <ref type="function">REALTIME_HASH</ref>
151  </see-also>
152  </function>
153  <function name="REALTIME_HASH" language="en_US">
154  <synopsis>
155  RealTime query function.
156  </synopsis>
157  <syntax>
158  <parameter name="family" required="true" />
159  <parameter name="fieldmatch" required="true" />
160  <parameter name="matchvalue" required="true" />
161  </syntax>
162  <description>
163  <para>This function retrieves a single record from the RT engine, where
164  <replaceable>fieldmatch</replaceable> contains the value
165  <replaceable>matchvalue</replaceable> and formats the output suitably, such that
166  it can be assigned to the HASH() function. The HASH() function then provides
167  a suitable method for retrieving each field value of the record.</para>
168  </description>
169  <see-also>
170  <ref type="function">REALTIME</ref>
171  <ref type="function">REALTIME_STORE</ref>
172  <ref type="function">REALTIME_DESTROY</ref>
173  <ref type="function">REALTIME_FIELD</ref>
174  </see-also>
175  </function>
176  ***/
177 
181 
182 static int function_realtime_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
183 {
184  struct ast_variable *var, *head;
185  struct ast_str *out;
186  size_t resultslen;
187  int n;
189  AST_APP_ARG(family);
190  AST_APP_ARG(fieldmatch);
192  AST_APP_ARG(delim1);
193  AST_APP_ARG(delim2);
194  );
195 
196  if (ast_strlen_zero(data)) {
197  ast_log(LOG_WARNING, "Syntax: REALTIME(family,fieldmatch[,matchvalue[,delim1[,delim2]]]) - missing argument!\n");
198  return -1;
199  }
200 
202 
203  if (!args.delim1)
204  args.delim1 = ",";
205  if (!args.delim2)
206  args.delim2 = "=";
207 
208  if (chan)
209  ast_autoservice_start(chan);
210 
211  head = ast_load_realtime_all(args.family, args.fieldmatch, args.value, SENTINEL);
212 
213  if (!head) {
214  if (chan)
215  ast_autoservice_stop(chan);
216  return -1;
217  }
218 
219  resultslen = 0;
220  n = 0;
221  for (var = head; var; n++, var = var->next)
222  resultslen += strlen(var->name) + strlen(var->value);
223  /* add space for delimiters and final '\0' */
224  resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1;
225 
226  if (resultslen > len) {
227  ast_log(LOG_WARNING, "Failed to fetch. Realtime data is too large: need %zu, have %zu.\n", resultslen, len);
228  if (chan) {
229  ast_autoservice_stop(chan);
230  }
231  return -1;
232  }
233 
234  /* len is going to be sensible, so we don't need to check for stack
235  * overflows here. */
236  out = ast_str_alloca(resultslen);
237  for (var = head; var; var = var->next)
238  ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1);
239  ast_copy_string(buf, ast_str_buffer(out), len);
240 
241  ast_variables_destroy(head);
242 
243  if (chan)
244  ast_autoservice_stop(chan);
245 
246  return 0;
247 }
248 
249 static int function_realtime_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
250 {
251  int res = 0;
253  AST_APP_ARG(family);
254  AST_APP_ARG(fieldmatch);
255  AST_APP_ARG(value);
256  AST_APP_ARG(field);
257  );
258 
259  if (ast_strlen_zero(data)) {
260  ast_log(LOG_WARNING, "Syntax: %s(family,fieldmatch,matchvalue,updatecol) - missing argument!\n", cmd);
261  return -1;
262  }
263 
265 
266  if (ast_strlen_zero(args.fieldmatch) || ast_strlen_zero(args.field)) {
267  ast_log(LOG_WARNING, "Syntax: %s(family,fieldmatch,matchvalue,updatecol) - missing argument!\n", cmd);
268  return -1;
269  }
270 
271  if (chan) {
272  ast_autoservice_start(chan);
273  }
274 
275  res = ast_update_realtime(args.family, args.fieldmatch, args.value, args.field, (char *)value, SENTINEL);
276 
277  if (res < 0) {
278  ast_log(LOG_WARNING, "Failed to update. Check the debug log for possible data repository related entries.\n");
279  }
280 
281  if (chan) {
282  ast_autoservice_stop(chan);
283  }
284 
285  return res;
286 }
287 
288 static int realtimefield_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
289 {
290  struct ast_variable *var, *head;
291  struct ast_str *escapebuf = ast_str_thread_get(&buf1, 16);
292  struct ast_str *fields = ast_str_thread_get(&buf2, 16);
293  struct ast_str *values = ast_str_thread_get(&buf3, 16);
294  int first = 0;
295  enum { rtfield, rthash } which;
297  AST_APP_ARG(family);
298  AST_APP_ARG(fieldmatch);
300  AST_APP_ARG(fieldname);
301  );
302 
303  if (!strcmp(cmd, "REALTIME_FIELD")) {
304  which = rtfield;
305  } else {
306  which = rthash;
307  }
308 
309  if (ast_strlen_zero(data)) {
310  ast_log(LOG_WARNING, "Syntax: %s(family,fieldmatch,matchvalue%s) - missing argument!\n", cmd, which == rtfield ? ",fieldname" : "");
311  return -1;
312  }
313 
315 
316  if ((which == rtfield && args.argc != 4) || (which == rthash && args.argc != 3)) {
317  ast_log(LOG_WARNING, "Syntax: %s(family,fieldmatch,matchvalue%s) - missing argument!\n", cmd, which == rtfield ? ",fieldname" : "");
318  return -1;
319  }
320 
321  if (chan) {
322  ast_autoservice_start(chan);
323  }
324 
325  if (!(head = ast_load_realtime_all(args.family, args.fieldmatch, args.value, SENTINEL))) {
326  if (chan) {
327  ast_autoservice_stop(chan);
328  }
329  return -1;
330  }
331 
332  ast_str_reset(fields);
333  ast_str_reset(values);
334 
335  for (var = head; var; var = var->next) {
336  if (which == rtfield) {
337  ast_debug(1, "Comparing %s to %s\n", var->name, args.fieldname);
338  if (!strcasecmp(var->name, args.fieldname)) {
339  ast_debug(1, "Match! Value is %s\n", var->value);
340  ast_copy_string(buf, var->value, len);
341  break;
342  }
343  } else if (which == rthash) {
344  ast_debug(1, "Setting hash key %s to value %s\n", var->name, var->value);
345  ast_str_append(&fields, 0, "%s%s", first ? "" : ",", ast_str_set_escapecommas(&escapebuf, 0, var->name, INT_MAX));
346  ast_str_append(&values, 0, "%s%s", first ? "" : ",", ast_str_set_escapecommas(&escapebuf, 0, var->value, INT_MAX));
347  first = 0;
348  }
349  }
350  ast_variables_destroy(head);
351 
352  if (which == rthash) {
353  pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
354  ast_copy_string(buf, ast_str_buffer(values), len);
355  }
356 
357  if (chan) {
358  ast_autoservice_stop(chan);
359  }
360 
361  return 0;
362 }
363 
364 static int function_realtime_store(struct ast_channel *chan, const char *cmd, char *data, const char *value)
365 {
366  int res = 0;
367  char storeid[32];
368  char *valcopy;
370  AST_APP_ARG(family);
371  AST_APP_ARG(f)[30]; /* fields */
372  );
373 
375  AST_APP_ARG(v)[30]; /* values */
376  );
377 
378  if (ast_strlen_zero(data)) {
379  ast_log(LOG_WARNING, "Syntax: REALTIME_STORE(family,field1,field2,...,field30) - missing argument!\n");
380  return -1;
381  }
382 
383  if (chan)
384  ast_autoservice_start(chan);
385 
386  valcopy = ast_strdupa(value);
387  AST_STANDARD_APP_ARGS(a, data);
388  AST_STANDARD_APP_ARGS(v, valcopy);
389 
390  res = ast_store_realtime(a.family,
391  a.f[0], v.v[0], a.f[1], v.v[1], a.f[2], v.v[2], a.f[3], v.v[3], a.f[4], v.v[4],
392  a.f[5], v.v[5], a.f[6], v.v[6], a.f[7], v.v[7], a.f[8], v.v[8], a.f[9], v.v[9],
393  a.f[10], v.v[10], a.f[11], v.v[11], a.f[12], v.v[12], a.f[13], v.v[13], a.f[14], v.v[14],
394  a.f[15], v.v[15], a.f[16], v.v[16], a.f[17], v.v[17], a.f[18], v.v[18], a.f[19], v.v[19],
395  a.f[20], v.v[20], a.f[21], v.v[21], a.f[22], v.v[22], a.f[23], v.v[23], a.f[24], v.v[24],
396  a.f[25], v.v[25], a.f[26], v.v[26], a.f[27], v.v[27], a.f[28], v.v[28], a.f[29], v.v[29], SENTINEL
397  );
398 
399  if (res < 0) {
400  ast_log(LOG_WARNING, "Failed to store. Check the debug log for possible data repository related entries.\n");
401  } else {
402  snprintf(storeid, sizeof(storeid), "%d", res);
403  pbx_builtin_setvar_helper(chan, "RTSTOREID", storeid);
404  }
405 
406  if (chan)
407  ast_autoservice_stop(chan);
408 
409  return 0;
410 }
411 
412 static int function_realtime_readdestroy(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
413 {
414  struct ast_variable *var, *head;
415  struct ast_str *out;
416  size_t resultslen;
417  int n;
419  AST_APP_ARG(family);
420  AST_APP_ARG(fieldmatch);
422  AST_APP_ARG(delim1);
423  AST_APP_ARG(delim2);
424  );
425 
426  if (ast_strlen_zero(data)) {
427  ast_log(LOG_WARNING, "Syntax: REALTIME_DESTROY(family,fieldmatch[,matchvalue[,delim1[,delim2]]]) - missing argument!\n");
428  return -1;
429  }
430 
432 
433  if (!args.delim1)
434  args.delim1 = ",";
435  if (!args.delim2)
436  args.delim2 = "=";
437 
438  if (chan)
439  ast_autoservice_start(chan);
440 
441  head = ast_load_realtime_all(args.family, args.fieldmatch, args.value, SENTINEL);
442 
443  if (!head) {
444  if (chan)
445  ast_autoservice_stop(chan);
446  return -1;
447  }
448 
449  if (len > 0) {
450  resultslen = 0;
451  n = 0;
452  for (var = head; var; n++, var = var->next) {
453  resultslen += strlen(var->name) + strlen(var->value);
454  }
455  /* add space for delimiters and final '\0' */
456  resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1;
457 
458  if (resultslen > len) {
459  /* Unfortunately this does mean that we cannot destroy
460  * the row anymore. But OTOH, we're not destroying
461  * someones data without giving him the chance to look
462  * at it. */
463  ast_log(LOG_WARNING, "Failed to fetch/destroy. Realtime data is too large: need %zu, have %zu.\n", resultslen, len);
464  if (chan) {
465  ast_autoservice_stop(chan);
466  }
467  return -1;
468  }
469 
470  /* len is going to be sensible, so we don't need to check for
471  * stack overflows here. */
472  out = ast_str_alloca(resultslen);
473  for (var = head; var; var = var->next) {
474  ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1);
475  }
476  ast_copy_string(buf, ast_str_buffer(out), len);
477  }
478 
479  ast_destroy_realtime(args.family, args.fieldmatch, args.value, SENTINEL);
480  ast_variables_destroy(head);
481 
482  if (chan)
483  ast_autoservice_stop(chan);
484 
485  return 0;
486 }
487 
488 /*!
489  * \brief Wrapper to execute REALTIME_DESTROY from a write operation. Allows
490  * execution even if live_dangerously is disabled.
491  */
492 static int function_realtime_writedestroy(struct ast_channel *chan, const char *cmd, char *data, const char *value)
493 {
494  return function_realtime_readdestroy(chan, cmd, data, NULL, 0);
495 }
496 
498  .name = "REALTIME",
499  .read = function_realtime_read,
500  .write = function_realtime_write,
501 };
502 
504  .name = "REALTIME_FIELD",
505  .read = realtimefield_read,
506  .write = function_realtime_write,
507 };
508 
510  .name = "REALTIME_HASH",
511  .read = realtimefield_read,
512 };
513 
515  .name = "REALTIME_STORE",
516  .write = function_realtime_store,
517 };
518 
520  .name = "REALTIME_DESTROY",
523 };
524 
525 static int unload_module(void)
526 {
527  int res = 0;
528  res |= ast_custom_function_unregister(&realtime_function);
529  res |= ast_custom_function_unregister(&realtime_store_function);
530  res |= ast_custom_function_unregister(&realtime_destroy_function);
531  res |= ast_custom_function_unregister(&realtimefield_function);
532  res |= ast_custom_function_unregister(&realtimehash_function);
533  return res;
534 }
535 
536 static int load_module(void)
537 {
538  int res = 0;
539  res |= ast_custom_function_register(&realtime_function);
540  res |= ast_custom_function_register(&realtime_store_function);
541  res |= ast_custom_function_register_escalating(&realtime_destroy_function, AST_CFE_READ);
542  res |= ast_custom_function_register(&realtimefield_function);
543  res |= ast_custom_function_register(&realtimehash_function);
544  return res;
545 }
546 
547 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Read/Write/Store/Destroy values from a RealTime repository");
const char * name
Definition: pbx.h:119
struct ast_variable * next
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:84
Main Channel structure associated with a channel.
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:567
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1263
static int load_module(void)
static struct ast_custom_function realtime_store_function
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
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
static struct ast_custom_function realtimehash_function
static int function_realtime_writedestroy(struct ast_channel *chan, const char *cmd, char *data, const char *value)
Wrapper to execute REALTIME_DESTROY from a write operation. Allows execution even if live_dangerously...
Structure for variables, used for configurations and for channel variables.
#define var
Definition: ast_expr2f.c:614
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1091
static struct ast_threadstorage buf2
#define ast_str_alloca(init_len)
Definition: strings.h:800
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
const char * args
#define NULL
Definition: resample.c:96
static int unload_module(void)
int value
Definition: syslog.c:37
int ast_update_realtime(const char *family, const char *keyfield, const char *lookup,...) attribute_sentinel
Update realtime configuration.
Definition: main/config.c:3489
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
static struct ast_custom_function realtime_function
Utility functions.
#define ast_strlen_zero(foo)
Definition: strings.h:52
static struct ast_custom_function realtime_destroy_function
static int function_realtime_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
Configuration File Parser.
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1517
static struct ast_custom_function realtimefield_function
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
#define SENTINEL
Definition: compiler.h:87
int ast_store_realtime(const char *family,...) attribute_sentinel
Create realtime configuration.
Definition: main/config.c:3570
General Asterisk PBX channel definitions.
static struct ast_threadstorage buf3
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
static int realtimefield_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
Core PBX routines and definitions.
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
static int function_realtime_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
struct sla_ringing_trunk * first
Definition: app_meetme.c:1092
static struct ast_threadstorage buf1
static int function_realtime_readdestroy(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
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...
char * ast_str_set_escapecommas(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Set a dynamic string to a non-NULL terminated substring, with escaping of commas. ...
Definition: strings.h:1021
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:653
FILE * out
Definition: utils/frame.c:33
int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup,...) attribute_sentinel
Destroy realtime configuration.
Definition: main/config.c:3606
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
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:861
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#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...
struct ast_variable * ast_load_realtime_all(const char *family,...) attribute_sentinel
Definition: main/config.c:3287
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1508
static int function_realtime_store(struct ast_channel *chan, const char *cmd, char *data, const char *value)
#define AST_APP_ARG(name)
Define an application argument.
static struct test_val a