2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief Full-featured outgoing call spool support
27 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
34 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
35 #include "asterisk/lock.h"
36 #include "asterisk/file.h"
37 #include "asterisk/logger.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/callerid.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/utils.h"
45 * pbx_spool is similar in spirit to qcall, but with substantially enhanced functionality...
46 * The spool file contains a header
50 /*! Always delete the call file after a call succeeds or the
51 * maximum number of retries is exceeded, even if the
52 * modification time of the call file is in the future.
54 SPOOL_FLAG_ALWAYS_DELETE
= (1 << 0),
55 /* Don't unlink the call file after processing, move in qdonedir */
56 SPOOL_FLAG_ARCHIVE
= (1 << 1)
59 static char qdir
[255];
60 static char qdonedir
[255];
64 int retries
; /*!< Current number of retries */
65 int maxretries
; /*!< Maximum number of retries permitted */
66 int retrytime
; /*!< How long to wait between retries (in seconds) */
67 int waittime
; /*!< How long to wait for an answer */
68 long callingpid
; /*!< PID which is currently calling */
69 int format
; /*!< Formats (codecs) for this call */
71 char tech
[256]; /*!< Which channel driver to use for outgoing call */
72 char dest
[256]; /*!< Which device/line to use for outgoing call */
74 char app
[256]; /*!< If application: Application name */
75 char data
[256]; /*!< If applicatoin: Application data */
77 char exten
[AST_MAX_EXTENSION
]; /*!< If extension/context/priority: Extension in dialplan */
78 char context
[AST_MAX_CONTEXT
]; /*!< If extension/context/priority: Dialplan context */
79 int priority
; /*!< If extension/context/priority: Dialplan priority */
81 char cid_num
[256]; /*!< CallerID Information: Number/extension */
82 char cid_name
[256]; /*!< CallerID Information: Name */
84 char account
[AST_MAX_ACCOUNT_CODE
]; /*!< account code */
86 struct ast_variable
*vars
; /*!< Variables and Functions */
88 int maxlen
; /*!< Maximum length of call */
90 struct ast_flags options
; /*!< options */
93 static void init_outgoing(struct outgoing
*o
)
98 o
->format
= AST_FORMAT_SLINEAR
;
99 ast_set_flag(&o
->options
, SPOOL_FLAG_ALWAYS_DELETE
);
102 static void free_outgoing(struct outgoing
*o
)
107 static int apply_outgoing(struct outgoing
*o
, char *fn
, FILE *f
)
112 struct ast_variable
*var
, *last
= o
->vars
;
114 while (last
&& last
->next
) {
118 while(fgets(buf
, sizeof(buf
), f
)) {
122 while ((c
= strchr(c
, '#'))) {
123 if ((c
== buf
) || (*(c
-1) == ' ') || (*(c
-1) == '\t'))
130 while ((c
= strchr(c
, ';'))) {
131 if ((c
> buf
) && (c
[-1] == '\\')) {
132 memmove(c
- 1, c
, strlen(c
) + 1);
140 /* Trim trailing white space */
141 while(!ast_strlen_zero(buf
) && buf
[strlen(buf
) - 1] < 33)
142 buf
[strlen(buf
) - 1] = '\0';
143 if (!ast_strlen_zero(buf
)) {
144 c
= strchr(buf
, ':');
148 while ((*c
) && (*c
< 33))
151 printf("'%s' is '%s' at line %d\n", buf
, c
, lineno
);
153 if (!strcasecmp(buf
, "channel")) {
154 ast_copy_string(o
->tech
, c
, sizeof(o
->tech
));
155 if ((c2
= strchr(o
->tech
, '/'))) {
158 ast_copy_string(o
->dest
, c2
, sizeof(o
->dest
));
160 ast_log(LOG_NOTICE
, "Channel should be in form Tech/Dest at line %d of %s\n", lineno
, fn
);
163 } else if (!strcasecmp(buf
, "callerid")) {
164 ast_callerid_split(c
, o
->cid_name
, sizeof(o
->cid_name
), o
->cid_num
, sizeof(o
->cid_num
));
165 } else if (!strcasecmp(buf
, "application")) {
166 ast_copy_string(o
->app
, c
, sizeof(o
->app
));
167 } else if (!strcasecmp(buf
, "data")) {
168 ast_copy_string(o
->data
, c
, sizeof(o
->data
));
169 } else if (!strcasecmp(buf
, "maxretries")) {
170 if (sscanf(c
, "%d", &o
->maxretries
) != 1) {
171 ast_log(LOG_WARNING
, "Invalid max retries at line %d of %s\n", lineno
, fn
);
174 } else if (!strcasecmp(buf
, "codecs")) {
175 ast_parse_allow_disallow(NULL
, &o
->format
, c
, 1);
176 } else if (!strcasecmp(buf
, "context")) {
177 ast_copy_string(o
->context
, c
, sizeof(o
->context
));
178 } else if (!strcasecmp(buf
, "extension")) {
179 ast_copy_string(o
->exten
, c
, sizeof(o
->exten
));
180 } else if (!strcasecmp(buf
, "priority")) {
181 if ((sscanf(c
, "%d", &o
->priority
) != 1) || (o
->priority
< 1)) {
182 ast_log(LOG_WARNING
, "Invalid priority at line %d of %s\n", lineno
, fn
);
185 } else if (!strcasecmp(buf
, "retrytime")) {
186 if ((sscanf(c
, "%d", &o
->retrytime
) != 1) || (o
->retrytime
< 1)) {
187 ast_log(LOG_WARNING
, "Invalid retrytime at line %d of %s\n", lineno
, fn
);
190 } else if (!strcasecmp(buf
, "waittime")) {
191 if ((sscanf(c
, "%d", &o
->waittime
) != 1) || (o
->waittime
< 1)) {
192 ast_log(LOG_WARNING
, "Invalid waittime at line %d of %s\n", lineno
, fn
);
195 } else if (!strcasecmp(buf
, "retry")) {
197 } else if (!strcasecmp(buf
, "startretry")) {
198 if (sscanf(c
, "%ld", &o
->callingpid
) != 1) {
199 ast_log(LOG_WARNING
, "Unable to retrieve calling PID!\n");
202 } else if (!strcasecmp(buf
, "endretry") || !strcasecmp(buf
, "abortretry")) {
205 } else if (!strcasecmp(buf
, "delayedretry")) {
206 } else if (!strcasecmp(buf
, "setvar") || !strcasecmp(buf
, "set")) {
210 var
= ast_variable_new(c
, c2
, fn
);
212 /* Always insert at the end, because some people want to treat the spool file as a script */
221 ast_log(LOG_WARNING
, "Malformed \"%s\" argument. Should be \"%s: variable=value\"\n", buf
, buf
);
222 } else if (!strcasecmp(buf
, "account")) {
223 ast_copy_string(o
->account
, c
, sizeof(o
->account
));
224 } else if (!strcasecmp(buf
, "alwaysdelete")) {
225 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ALWAYS_DELETE
);
226 } else if (!strcasecmp(buf
, "archive")) {
227 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ARCHIVE
);
229 ast_log(LOG_WARNING
, "Unknown keyword '%s' at line %d of %s\n", buf
, lineno
, fn
);
232 ast_log(LOG_NOTICE
, "Syntax error at line %d of %s\n", lineno
, fn
);
235 ast_copy_string(o
->fn
, fn
, sizeof(o
->fn
));
236 if (ast_strlen_zero(o
->tech
) || ast_strlen_zero(o
->dest
) || (ast_strlen_zero(o
->app
) && ast_strlen_zero(o
->exten
))) {
237 ast_log(LOG_WARNING
, "At least one of app or extension must be specified, along with tech and dest in file %s\n", fn
);
243 static void safe_append(struct outgoing
*o
, time_t now
, char *s
)
249 if ((fd
= open(o
->fn
, O_WRONLY
| O_APPEND
)) < 0)
252 if ((f
= fdopen(fd
, "a"))) {
253 fprintf(f
, "\n%s: %ld %d (%ld)\n", s
, (long)ast_mainpid
, o
->retries
, (long) now
);
258 /* Update the file time */
260 tbuf
.modtime
= now
+ o
->retrytime
;
261 if (utime(o
->fn
, &tbuf
))
262 ast_log(LOG_WARNING
, "Unable to set utime on %s: %s\n", o
->fn
, strerror(errno
));
266 * \brief Remove a call file from the outgoing queue optionally moving it in the archive dir
268 * \param o the pointer to outgoing struct
269 * \param status the exit status of the call. Can be "Completed", "Failed" or "Expired"
271 static int remove_from_queue(struct outgoing
*o
, const char *status
)
278 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ALWAYS_DELETE
)) {
279 struct stat current_file_status
;
281 if (!stat(o
->fn
, ¤t_file_status
)) {
282 if (time(NULL
) < current_file_status
.st_mtime
)
287 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ARCHIVE
)) {
292 if (ast_mkdir(qdonedir
, 0777)) {
293 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool archiving disabled\n", qdonedir
);
298 if ((fd
= open(o
->fn
, O_WRONLY
| O_APPEND
))) {
299 if ((f
= fdopen(fd
, "a"))) {
300 fprintf(f
, "Status: %s\n", status
);
306 if (!(bname
= strrchr(o
->fn
, '/')))
310 snprintf(newfn
, sizeof(newfn
), "%s/%s", qdonedir
, bname
);
311 /* a existing call file the archive dir is overwritten */
313 if (rename(o
->fn
, newfn
) != 0) {
320 static void *attempt_thread(void *data
)
322 struct outgoing
*o
= data
;
324 if (!ast_strlen_zero(o
->app
)) {
325 ast_verb(3, "Attempting call on %s/%s for application %s(%s) (Retry %d)\n", o
->tech
, o
->dest
, o
->app
, o
->data
, o
->retries
);
326 res
= ast_pbx_outgoing_app(o
->tech
, o
->format
, o
->dest
, o
->waittime
* 1000, o
->app
, o
->data
, &reason
, 2 /* wait to finish */, o
->cid_num
, o
->cid_name
, o
->vars
, o
->account
, NULL
);
328 ast_verb(3, "Attempting call on %s/%s for %s@%s:%d (Retry %d)\n", o
->tech
, o
->dest
, o
->exten
, o
->context
,o
->priority
, o
->retries
);
329 res
= ast_pbx_outgoing_exten(o
->tech
, o
->format
, o
->dest
, o
->waittime
* 1000, o
->context
, o
->exten
, o
->priority
, &reason
, 2 /* wait to finish */, o
->cid_num
, o
->cid_name
, o
->vars
, o
->account
, NULL
);
332 ast_log(LOG_NOTICE
, "Call failed to go through, reason (%d) %s\n", reason
, ast_channel_reason2str(reason
));
333 if (o
->retries
>= o
->maxretries
+ 1) {
334 /* Max retries exceeded */
335 ast_log(LOG_EVENT
, "Queued call to %s/%s expired without completion after %d attempt%s\n", o
->tech
, o
->dest
, o
->retries
- 1, ((o
->retries
- 1) != 1) ? "s" : "");
336 remove_from_queue(o
, "Expired");
338 /* Notate that the call is still active */
339 safe_append(o
, time(NULL
), "EndRetry");
342 ast_log(LOG_NOTICE
, "Call completed to %s/%s\n", o
->tech
, o
->dest
);
343 ast_log(LOG_EVENT
, "Queued call to %s/%s completed\n", o
->tech
, o
->dest
);
344 remove_from_queue(o
, "Completed");
350 static void launch_service(struct outgoing
*o
)
355 if ((ret
= ast_pthread_create_detached(&t
, NULL
, attempt_thread
, o
))) {
356 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
361 static int scan_service(char *fn
, time_t now
, time_t atime
)
363 struct outgoing
*o
= NULL
;
367 if (!(o
= ast_calloc(1, sizeof(*o
)))) {
368 ast_log(LOG_WARNING
, "Out of memory ;(\n");
374 /* Attempt to open the file */
375 if (!(f
= fopen(fn
, "r+"))) {
376 remove_from_queue(o
, "Failed");
378 ast_log(LOG_WARNING
, "Unable to open %s: %s, deleting\n", fn
, strerror(errno
));
382 /* Read in and verify the contents */
383 if (apply_outgoing(o
, fn
, f
)) {
384 remove_from_queue(o
, "Failed");
386 ast_log(LOG_WARNING
, "Invalid file contents in %s, deleting\n", fn
);
392 printf("Filename: %s, Retries: %d, max: %d\n", fn
, o
->retries
, o
->maxretries
);
395 if (o
->retries
<= o
->maxretries
) {
397 if (o
->callingpid
&& (o
->callingpid
== ast_mainpid
)) {
398 safe_append(o
, time(NULL
), "DelayedRetry");
399 ast_log(LOG_DEBUG
, "Delaying retry since we're currently running '%s'\n", o
->fn
);
402 /* Increment retries */
404 /* If someone else was calling, they're presumably gone now
405 so abort their retry and continue as we were... */
407 safe_append(o
, time(NULL
), "AbortRetry");
409 safe_append(o
, now
, "StartRetry");
414 ast_log(LOG_EVENT
, "Queued call to %s/%s expired without completion after %d attempt%s\n", o
->tech
, o
->dest
, o
->retries
- 1, ((o
->retries
- 1) != 1) ? "s" : "");
415 remove_from_queue(o
, "Expired");
422 static void *scan_thread(void *unused
)
429 time_t last
= 0, next
= 0, now
;
436 if (stat(qdir
, &st
)) {
437 ast_log(LOG_WARNING
, "Unable to stat %s\n", qdir
);
441 /* Make sure it is time for us to execute our check */
442 if ((st
.st_mtime
== last
) && (next
&& (next
> now
)))
446 printf("atime: %ld, mtime: %ld, ctime: %ld\n", st
.st_atime
, st
.st_mtime
, st
.st_ctime
);
447 printf("Ooh, something changed / timeout\n");
452 if (!(dir
= opendir(qdir
))) {
453 ast_log(LOG_WARNING
, "Unable to open directory %s: %s\n", qdir
, strerror(errno
));
457 while ((de
= readdir(dir
))) {
458 snprintf(fn
, sizeof(fn
), "%s/%s", qdir
, de
->d_name
);
460 ast_log(LOG_WARNING
, "Unable to stat %s: %s\n", fn
, strerror(errno
));
463 if (!S_ISREG(st
.st_mode
))
465 if (st
.st_mtime
<= now
) {
466 res
= scan_service(fn
, now
, st
.st_atime
);
468 /* Update next service time */
469 if (!next
|| (res
< next
)) {
473 ast_log(LOG_WARNING
, "Failed to scan service '%s'\n", fn
);
475 /* Update "next" update if necessary */
476 if (!next
|| (st
.st_mtime
< next
))
485 static int unload_module(void)
490 static int load_module(void)
494 snprintf(qdir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing");
495 if (ast_mkdir(qdir
, 0777)) {
496 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool disabled\n", qdir
);
497 return AST_MODULE_LOAD_DECLINE
;
499 snprintf(qdonedir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing_done");
501 if ((ret
= ast_pthread_create_detached_background(&thread
, NULL
, scan_thread
, NULL
))) {
502 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
503 return AST_MODULE_LOAD_FAILURE
;
506 return AST_MODULE_LOAD_SUCCESS
;
509 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Outgoing Spool Support");