Asterisk - The Open Source Telephony Project  21.4.1
app_waitforcond.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2021, Naveen Albert
5  *
6  * Naveen Albert <asterisk@phreaknet.org>
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 Sleep until a condition is true
22  *
23  * \author Naveen Albert <asterisk@phreaknet.org>
24  *
25  * \ingroup applications
26  */
27 
28 /*** MODULEINFO
29  <support_level>extended</support_level>
30  ***/
31 
32 #include "asterisk.h"
33 
34 #include "asterisk/logger.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/module.h"
38 #include "asterisk/app.h"
39 
40 /*** DOCUMENTATION
41  <application name="WaitForCondition" language="en_US">
42  <since>
43  <version>16.20.0</version>
44  <version>18.6.0</version>
45  <version>19.0.0</version>
46  </since>
47  <synopsis>
48  Wait (sleep) until the given condition is true.
49  </synopsis>
50  <syntax>
51  <parameter name="replacementchar" required="true">
52  <para>Specifies the character in the expression used to replace the <literal>$</literal>
53  character. This character should not be used anywhere in the expression itself.</para>
54  </parameter>
55  <parameter name="expression" required="true">
56  <para>A modified logical expression with the <literal>$</literal> characters replaced by
57  <replaceable>replacementchar</replaceable>. This is necessary to pass the expression itself
58  into the application, rather than its initial evaluation.</para>
59  </parameter>
60  <parameter name="timeout">
61  <para>The maximum amount of time, in seconds, this application should wait for a condition
62  to become true before dialplan execution continues automatically to the next priority.
63  By default, there is no timeout.</para>
64  </parameter>
65  <parameter name="interval">
66  <para>The frequency, in seconds, of polling the condition, which can be adjusted depending
67  on how time-sensitive execution needs to be. By default, this is 0.05.</para>
68  </parameter>
69  </syntax>
70  <description>
71  <para>Waits until <replaceable>expression</replaceable> evaluates to true, checking every
72  <replaceable>interval</replaceable> seconds for up to <replaceable>timeout</replaceable>. Default
73  is evaluate <replaceable>expression</replaceable> every 50 milliseconds with no timeout.</para>
74  <example title="Wait for condition dialplan variable/function to become 1 for up to 40 seconds, checking every 500ms">
75  same => n,WaitForCondition(#,#["#{condition}"="1"],40,0.5)
76  </example>
77  <para>Sets <variable>WAITFORCONDITIONSTATUS</variable> to one of the following values:</para>
78  <variablelist>
79  <variable name="WAITFORCONDITIONSTATUS">
80  <value name="TRUE">
81  Condition evaluated to true before timeout expired.
82  </value>
83  <value name="FAILURE">
84  Invalid argument.
85  </value>
86  <value name="TIMEOUT">
87  Timeout elapsed without condition evaluating to true.
88  </value>
89  <value name="HANGUP">
90  Channel hung up before condition became true.
91  </value>
92  </variable>
93  </variablelist>
94  </description>
95  </application>
96  ***/
97 
98 static char *app = "WaitForCondition";
99 
100 static int waitforcond_exec(struct ast_channel *chan, const char *data)
101 {
102  int ms, i;
103  double timeout = 0, poll = 0;
104  int timeout_ms = 0;
105  int poll_ms = 50; /* default is evaluate the condition every 50ms */
106  struct timeval start = ast_tvnow();
107  char dollarsignrep;
108  int brackets = 0;
109  char *pos, *open_bracket, *expression, *optargs = NULL;
110  char condition[512];
111 
113  AST_APP_ARG(timeout);
114  AST_APP_ARG(interval);
115  );
116 
117  pos = ast_strdupa(data);
118 
119  if (ast_strlen_zero(pos)) {
120  ast_log(LOG_ERROR, "WaitForCondition requires a condition\n");
121  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
122  return 0;
123  }
124 
125  /* is there at least a [ followed by a ] somewhere ? */
126  if (!(open_bracket = strchr(pos, '[')) || !strchr(open_bracket, ']')) {
127  ast_log(LOG_ERROR, "No expression detected. Did you forget to replace the $ signs?\n");
128  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
129  return 0;
130  }
131 
132  dollarsignrep = pos[0];
133  if (dollarsignrep == '$' || dollarsignrep == '[' || dollarsignrep == ']'
134  || dollarsignrep == '{' || dollarsignrep == '}') {
135  ast_log(LOG_ERROR, "Dollar sign replacement cannot be %c.\n", dollarsignrep);
136  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
137  return 0;
138  }
139  ++pos;
140  if (pos[0] != ',') {
141  ast_log(LOG_ERROR, "Invalid separator: %c\n", pos[0]);
142  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
143  return 0;
144  }
145  ++pos;
146  if (pos[0] != dollarsignrep) {
147  ast_log(LOG_ERROR, "Expression start does not match provided replacement: %c\n", pos[0]);
148  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
149  return 0;
150  }
151 
152  expression = pos; /* we're at the start of the expression */
153 
154  /* commas may appear within the expression, so go until we've encountered as many closing brackets as opening */
155  while (++pos) {
156  if (pos[0] == '\0') {
157  ast_log(LOG_ERROR, "Could not parse end of expression.\n");
158  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
159  return 0;
160  }
161  if (pos[0] == '[') {
162  brackets++;
163  } else if (pos[0] == ']') {
164  brackets--;
165  }
166  if (brackets == 0) { /* reached end of expression */
167  break;
168  }
169  }
170  ++pos;
171  if (pos[0] != '\0') {
172  ++pos; /* eat comma separator */
173  if (pos[0] != '\0') {
174  optargs = ast_strdupa(pos);
175  AST_STANDARD_APP_ARGS(args, optargs);
176  if (!ast_strlen_zero(args.timeout)) {
177  if (sscanf(args.timeout, "%30lg", &timeout) != 1) {
178  ast_log(LOG_WARNING, "Invalid timeout provided: %s. No timeout set.\n", args.timeout);
179  return -1;
180  }
181  timeout_ms = timeout * 1000.0;
182  }
183 
184  if (!ast_strlen_zero(args.interval)) {
185  if (sscanf(args.interval, "%30lg", &poll) != 1) {
186  ast_log(LOG_WARNING, "Invalid polling interval provided: %s. Default unchanged.\n", args.interval);
187  return -1;
188  }
189  if (poll < 0.001) {
190  ast_log(LOG_WARNING, "Polling interval cannot be less than 1ms. Default unchanged.\n");
191  return -1;
192  }
193  poll_ms = poll * 1000.0;
194  }
195  }
196  }
197 
198  for (i = 0; expression[i] != '\0'; i++) {
199  if (expression[i] == dollarsignrep) {
200  expression[i] = '$'; /* replace $s back into expression for variable parsing */
201  }
202  }
203 
204  if (timeout_ms > 0) {
205  ast_debug(1, "Waiting for condition for %f seconds: %s (checking every %d ms)", timeout, expression, poll_ms);
206  } else {
207  ast_debug(1, "Waiting for condition, forever: %s (checking every %d ms)", expression, poll_ms);
208  }
209 
210  while (1) {
211  /* Substitute variables now */
212  pbx_substitute_variables_helper(chan, expression, condition, sizeof(condition) - 1);
213  if (pbx_checkcondition(condition)) {
214  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "TRUE");
215  return 0;
216  }
217  /* If a timeout was specified, check that it hasn't expired */
218  if ((timeout_ms > 0) && !(ms = ast_remaining_ms(start, timeout_ms))) {
219  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "TIMEOUT");
220  return 0;
221  }
222  if (ast_safe_sleep(chan, poll_ms)) { /* don't waste CPU, we don't need a super tight loop */
223  pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "HANGUP");
224  return -1; /* channel hung up */
225  }
226  }
227 }
228 
229 static int unload_module(void)
230 {
231  return ast_unregister_application(app);
232 }
233 
234 static int load_module(void)
235 {
236  return ast_register_application_xml(app, waitforcond_exec);
237 }
238 
239 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Wait until condition is true");
int ast_safe_sleep(struct ast_channel *chan, int ms)
Wait for a specified amount of time, looking for hangups.
Definition: channel.c:1574
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
int pbx_checkcondition(const char *condition)
Evaluate a condition.
Definition: pbx.c:8282
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_debug(level,...)
Log a DEBUG message.
Core PBX routines and definitions.
int ast_remaining_ms(struct timeval start, int max_ms)
Calculate remaining milliseconds given a starting timestamp and upper bound.
Definition: utils.c:2281
Support for logging to various files, console and syslog Configuration in file logger.conf.
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...
#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's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
#define AST_APP_ARG(name)
Define an application argument.