Asterisk - The Open Source Telephony Project  21.4.1
app_signal.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2022, 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 Channel signaling applications
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/file.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/module.h"
38 #include "asterisk/app.h"
39 #include "asterisk/module.h"
40 
41 /*** DOCUMENTATION
42  <application name="Signal" language="en_US">
43  <synopsis>
44  Sends a signal to any waiting channels.
45  </synopsis>
46  <syntax>
47  <parameter name="signalname" required="true">
48  <para>Name of signal to send.</para>
49  </parameter>
50  <parameter name="payload" required="false">
51  <para>Payload data to deliver.</para>
52  </parameter>
53  </syntax>
54  <description>
55  <para>Sends a named signal to any channels that may be
56  waiting for one. Acts as a producer in a simple
57  message queue.</para>
58  <variablelist>
59  <variable name="SIGNALSTATUS">
60  <value name="SUCCESS">
61  Signal was successfully sent to at least
62  one listener for processing.
63  </value>
64  <value name="FAILURE">
65  Signal could not be sent or nobody
66  was listening for this signal.
67  </value>
68  </variable>
69  </variablelist>
70  <example title="Send a signal named workdone">
71  same => n,Signal(workdone,Work has completed)
72  </example>
73  </description>
74  <see-also>
75  <ref type="application">WaitForSignal</ref>
76  </see-also>
77  </application>
78  <application name="WaitForSignal" language="en_US">
79  <synopsis>
80  Waits for a named signal on a channel.
81  </synopsis>
82  <syntax>
83  <parameter name="signalname" required="true">
84  <para>Name of signal to send.</para>
85  </parameter>
86  <parameter name="signaltimeout" required="false">
87  <para>Maximum time, in seconds, to wait for signal.</para>
88  </parameter>
89  </syntax>
90  <description>
91  <para>Waits for <replaceable>signaltimeout</replaceable> seconds on the current
92  channel to receive a signal with name <replaceable>signalname</replaceable>.
93  Acts as a consumer in a simple message queue.</para>
94  <para>Result of signal wait will be stored in the following variables:</para>
95  <variablelist>
96  <variable name="WAITFORSIGNALSTATUS">
97  <value name="SIGNALED">
98  Signal was received.
99  </value>
100  <value name="TIMEOUT">
101  Timed out waiting for signal.
102  </value>
103  <value name="HANGUP">
104  Channel hung up before signal was received.
105  </value>
106  </variable>
107  <variable name="WAITFORSIGNALPAYLOAD">
108  <para>Data payload attached to signal, if it exists</para>
109  </variable>
110  </variablelist>
111  <example title="Wait for the workdone signal, indefinitely, and print out payload">
112  same => n,WaitForSignal(workdone)
113  same => n,NoOp(Received: ${WAITFORSIGNALPAYLOAD})
114  </example>
115  </description>
116  <see-also>
117  <ref type="application">Signal</ref>
118  </see-also>
119  </application>
120  ***/
121 
122 static const char * const app = "Signal";
123 static const char * const app2 = "WaitForSignal";
124 
125 struct signalitem {
126  ast_mutex_t lock;
127  char name[AST_MAX_CONTEXT];
128  int sig_alert_pipe[2];
129  int watchers;
130  unsigned int signaled:1;
131  char *payload;
132  AST_LIST_ENTRY(signalitem) entry; /*!< Next Signal item */
133 };
134 
136 
137 static struct signalitem *alloc_signal(const char *sname)
138 {
139  struct signalitem *s;
140 
141  if (!(s = ast_calloc(1, sizeof(*s)))) {
142  return NULL;
143  }
144 
145  ast_mutex_init(&s->lock);
146  ast_copy_string(s->name, sname, sizeof(s->name));
147 
148  s->sig_alert_pipe[0] = -1;
149  s->sig_alert_pipe[1] = -1;
150  s->watchers = 0;
151  s->payload = NULL;
152  ast_alertpipe_init(s->sig_alert_pipe);
153 
154  return s;
155 }
156 
157 static int dealloc_signal(struct signalitem *s)
158 {
159  if (s->watchers) { /* somebody is still using us... refuse to go away */
160  ast_debug(1, "Signal '%s' is still being used by %d listener(s)\n", s->name, s->watchers);
161  return -1;
162  }
163  ast_alertpipe_close(s->sig_alert_pipe);
164  ast_mutex_destroy(&s->lock);
165  if (s->payload) {
166  ast_free(s->payload);
167  s->payload = NULL;
168  }
169  ast_free(s);
170  s = NULL;
171  return 0;
172 }
173 
174 static int remove_signal(char *sname)
175 {
176  int res = -1;
177  struct signalitem *s;
178 
180  if (!strcmp(s->name, sname)) {
182  res = dealloc_signal(s);
183  ast_debug(1, "Removed signal '%s'\n", sname);
184  }
185  }
187 
188  return res;
189 }
190 
191 static struct signalitem *get_signal(char *sname, int addnew)
192 {
193  struct signalitem *s = NULL;
196  if (!strcasecmp(s->name, sname)) {
197  ast_debug(1, "Using existing signal item '%s'\n", sname);
198  break;
199  }
200  }
201  if (!s) {
202  if (addnew) { /* signal doesn't exist, so create it */
203  s = alloc_signal(sname);
204  /* Totally fail if we fail to find/create an entry */
205  if (s) {
206  ast_debug(1, "Created new signal item '%s'\n", sname);
207  AST_RWLIST_INSERT_HEAD(&signals, s, entry);
208  } else {
209  ast_log(LOG_WARNING, "Failed to create signal item for '%s'\n", sname);
210  }
211  } else {
212  ast_debug(1, "Signal '%s' doesn't exist, and not creating it\n", sname);
213  }
214  }
216  return s;
217 }
218 
219 static int wait_for_signal_or_hangup(struct ast_channel *chan, char *signame, int timeout)
220 {
221  struct signalitem *s = NULL;
222  int ms, remaining_time, res = 1, goaway = 0;
223  struct timeval start;
224  struct ast_frame *frame = NULL;
225 
226  remaining_time = timeout;
227  start = ast_tvnow();
228 
229  s = get_signal(signame, 1);
230 
231  ast_mutex_lock(&s->lock);
232  s->watchers = s->watchers + 1; /* we unlock, because a) other people need to use this and */
233  ast_mutex_unlock(&s->lock); /* b) the signal will be available to us as long as watchers > 0 */
234 
235  while (timeout == 0 || remaining_time > 0) {
236  int ofd, exception;
237 
238  ms = 1000;
239  errno = 0;
240  if (ast_waitfor_nandfds(&chan, 1, &s->sig_alert_pipe[0], 1, &exception, &ofd, &ms)) { /* channel won */
241  if (!(frame = ast_read(chan))) { /* channel hung up */
242  ast_debug(1, "Channel '%s' did not return a frame; probably hung up.\n", ast_channel_name(chan));
243  res = -1;
244  break;
245  } else {
246  ast_frfree(frame); /* handle frames */
247  }
248  } else if (ofd == s->sig_alert_pipe[0]) { /* fd won */
249  if (ast_alertpipe_read(s->sig_alert_pipe) == AST_ALERT_READ_SUCCESS) {
250  ast_debug(1, "Alert pipe has data for us\n");
251  res = 0;
252  break;
253  } else {
254  ast_debug(1, "Alert pipe does not have data for us\n");
255  }
256  } else { /* nobody won */
257  if (ms && (ofd < 0)) {
258  if (!((errno == 0) || (errno == EINTR))) {
259  ast_log(LOG_WARNING, "Something bad happened while channel '%s' was polling.\n", ast_channel_name(chan));
260  break;
261  }
262  } /* else, nothing happened */
263  }
264  if (timeout) {
265  remaining_time = ast_remaining_ms(start, timeout);
266  }
267  }
268 
269  /* WRLOCK the list so that if we're going to destroy the signal now, nobody else can grab it before that happens. */
271  ast_mutex_lock(&s->lock);
272  if (s->payload) {
273  pbx_builtin_setvar_helper(chan, "WAITFORSIGNALPAYLOAD", s->payload);
274  }
275  s->watchers = s->watchers - 1;
276  if (s->watchers) { /* folks are still waiting for this, pass it on... */
277  int save_errno = errno;
278  if (ast_alertpipe_write(s->sig_alert_pipe)) {
279  ast_log(LOG_WARNING, "%s: write() failed: %s\n", __FUNCTION__, strerror(errno));
280  }
281  errno = save_errno;
282  } else { /* nobody else is waiting for this */
283  goaway = 1; /* we were the last guy using this, so mark signal item for destruction */
284  }
285  ast_mutex_unlock(&s->lock);
286 
287  if (goaway) {
288  /* remove_signal calls ast_mutex_destroy, so don't call it with the mutex itself locked. */
289  remove_signal(signame);
290  }
292 
293  return res;
294 }
295 
296 static int send_signal(char *signame, char *payload)
297 {
298  struct signalitem *s;
299  int save_errno = errno;
300  int res = 0;
301 
302  s = get_signal(signame, 0); /* if signal doesn't exist already, no point in creating it, because nobody could be waiting for it! */
303 
304  if (!s) {
305  return -1; /* this signal didn't exist, so we can't send a signal for it */
306  }
307 
308  /* at this point, we know someone is listening, since signals are destroyed when watchers gets down to 0 */
309  ast_mutex_lock(&s->lock);
310  s->signaled = 1;
311  if (payload && *payload) {
312  int len = strlen(payload);
313  if (s->payload) {
314  ast_free(s->payload); /* if there was already a payload, replace it */
315  s->payload = NULL;
316  }
317  s->payload = ast_malloc(len + 1);
318  if (!s->payload) {
319  ast_log(LOG_WARNING, "Failed to allocate signal payload '%s'\n", payload);
320  } else {
321  ast_copy_string(s->payload, payload, len + 1);
322  }
323  }
324  if (ast_alertpipe_write(s->sig_alert_pipe)) {
325  ast_log(LOG_WARNING, "%s: write() failed: %s\n", __FUNCTION__, strerror(errno));
326  s->signaled = 0; /* okay, so we didn't send a signal after all... */
327  res = -1;
328  }
329  errno = save_errno;
330  ast_debug(1, "Sent '%s' signal to %d listeners\n", signame, s->watchers);
331  ast_mutex_unlock(&s->lock);
332 
333  return res;
334 }
335 
336 static int waitsignal_exec(struct ast_channel *chan, const char *data)
337 {
338  char *argcopy;
339  int r = 0, timeoutms = 0;
340  double timeout = 0;
341 
343  AST_APP_ARG(signame);
344  AST_APP_ARG(sigtimeout);
345  );
346 
347  if (ast_strlen_zero(data)) {
348  ast_log(LOG_WARNING, "Signal() requires arguments\n");
349  return -1;
350  }
351 
352  argcopy = ast_strdupa(data);
353  AST_STANDARD_APP_ARGS(args, argcopy);
354 
355  if (ast_strlen_zero(args.signame)) {
356  ast_log(LOG_WARNING, "Missing signal name\n");
357  return -1;
358  }
359  if (strlen(args.signame) >= AST_MAX_CONTEXT) {
360  ast_log(LOG_WARNING, "Signal name '%s' is too long\n", args.signame);
361  return -1;
362  }
363  if (!ast_strlen_zero(args.sigtimeout)) {
364  if (sscanf(args.sigtimeout, "%30lg", &timeout) != 1 || timeout < 0) {
365  ast_log(LOG_WARNING, "Invalid timeout provided: %s. Defaulting to no timeout.\n", args.sigtimeout);
366  } else {
367  timeoutms = timeout * 1000; /* sec to msec */
368  }
369  }
370 
371  if (timeout > 0) {
372  ast_debug(1, "Waiting for signal '%s' for %d ms\n", args.signame, timeoutms);
373  } else {
374  ast_debug(1, "Waiting for signal '%s', indefinitely\n", args.signame);
375  }
376 
377  r = wait_for_signal_or_hangup(chan, args.signame, timeoutms);
378 
379  if (r == 1) {
380  ast_verb(3, "Channel '%s' timed out, waiting for signal '%s'\n", ast_channel_name(chan), args.signame);
381  pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "TIMEOUT");
382  } else if (!r) {
383  ast_verb(3, "Received signal '%s' on channel '%s'\n", args.signame, ast_channel_name(chan));
384  pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "SIGNALED");
385  } else {
386  pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "HANGUP");
387  ast_verb(3, "Channel '%s' hung up\n", ast_channel_name(chan));
388  return -1;
389  }
390 
391  return 0;
392 }
393 
394 static int signal_exec(struct ast_channel *chan, const char *data)
395 {
396  char *argcopy;
398  AST_APP_ARG(signame);
399  AST_APP_ARG(payload);
400  );
401 
402  if (ast_strlen_zero(data)) {
403  ast_log(LOG_WARNING, "Signal() requires arguments\n");
404  return -1;
405  }
406 
407  argcopy = ast_strdupa(data);
408  AST_STANDARD_APP_ARGS(args, argcopy);
409 
410  if (ast_strlen_zero(args.signame)) {
411  ast_log(LOG_WARNING, "Missing signal name\n");
412  return -1;
413  }
414  if (strlen(args.signame) >= AST_MAX_CONTEXT) {
415  ast_log(LOG_WARNING, "Signal name '%s' is too long\n", args.signame);
416  return -1;
417  }
418 
419  if (send_signal(args.signame, args.payload)) {
420  pbx_builtin_setvar_helper(chan, "SIGNALSTATUS", "FAILURE");
421  } else {
422  pbx_builtin_setvar_helper(chan, "SIGNALSTATUS", "SUCCESS");
423  }
424 
425  return 0;
426 }
427 
428 static int unload_module(void)
429 {
430  struct signalitem *s;
431  int res = 0;
432 
433  /* To avoid a locking nightmare, and for logistical reasons, this module
434  * will refuse to unload if watchers > 0. That way we know a signal's
435  * pipe won't disappear while it's being used. */
436 
438  /* Don't just use AST_RWLIST_REMOVE_HEAD, because if dealloc_signal fails, it should stay in the list. */
440  int mres = dealloc_signal(s);
441  res |= mres;
442  if (!mres) {
444  }
445  }
448 
449  /* One or more signals still has watchers. */
450  if (res) {
451  ast_log(LOG_WARNING, "One or more signals is currently in use. Unload failed.\n");
452  return res;
453  }
454 
455  res |= ast_unregister_application(app);
456  res |= ast_unregister_application(app2);
457 
458  return res;
459 }
460 
461 static int load_module(void)
462 {
463  int res;
464 
465  res = ast_register_application_xml(app, signal_exec);
466  res |= ast_register_application_xml(app2, waitsignal_exec);
467 
468  return res;
469 }
470 
471 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Channel Signaling Applications");
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.
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4257
void ast_alertpipe_close(int alert_pipe[2])
Close an alert pipe.
Definition: alertpipe.c:79
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
struct ast_channel * ast_waitfor_nandfds(struct ast_channel **c, int n, int *fds, int nfds, int *exception, int *outfd, int *ms)
Waits for activity on a group of channels.
Definition: channel.c:2988
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized...
Definition: linkedlists.h:333
struct signalitem::@62 entry
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
ast_alert_status_t ast_alertpipe_read(int alert_pipe[2])
Read an event from an alert pipe.
Definition: alertpipe.c:102
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_debug(level,...)
Log a DEBUG message.
Core PBX routines and definitions.
int ast_alertpipe_init(int alert_pipe[2])
Initialize an alert pipe.
Definition: alertpipe.c:38
int ast_remaining_ms(struct timeval start, int max_ms)
Calculate remaining milliseconds given a starting timestamp and upper bound.
Definition: utils.c:2281
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_MAX_CONTEXT
Definition: channel.h:135
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
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...
ssize_t ast_alertpipe_write(int alert_pipe[2])
Write an event to an alert pipe.
Definition: alertpipe.c:120
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
Data structure associated with a single frame of data.
Definition: search.h:40
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#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
Structure for mutex and tracking information.
Definition: lock.h:135
#define AST_APP_ARG(name)
Define an application argument.