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$")
41 #include "asterisk/lock.h"
42 #include "asterisk/file.h"
43 #include "asterisk/logger.h"
44 #include "asterisk/channel.h"
45 #include "asterisk/callerid.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/options.h"
49 #include "asterisk/utils.h"
52 * pbx_spool is similar in spirit to qcall, but with substantially enhanced functionality...
53 * The spool file contains a header
57 /*! Always delete the call file after a call succeeds or the
58 * maximum number of retries is exceeded, even if the
59 * modification time of the call file is in the future.
61 SPOOL_FLAG_ALWAYS_DELETE
= (1 << 0),
62 /* Don't unlink the call file after processing, move in qdonedir */
63 SPOOL_FLAG_ARCHIVE
= (1 << 1)
66 static char qdir
[255];
67 static char qdonedir
[255];
71 /* Current number of retries */
73 /* Maximum number of retries permitted */
75 /* How long to wait between retries (in seconds) */
77 /* How long to wait for an answer */
79 /* PID which is currently calling */
82 /* What to connect to outgoing */
90 /* If extension/context/priority */
95 /* CallerID Information */
100 char account
[AST_MAX_ACCOUNT_CODE
];
102 /* Variables and Functions */
103 struct ast_variable
*vars
;
105 /* Maximum length of call */
109 struct ast_flags options
;
112 static void init_outgoing(struct outgoing
*o
)
114 memset(o
, 0, sizeof(struct outgoing
));
118 ast_set_flag(&o
->options
, SPOOL_FLAG_ALWAYS_DELETE
);
121 static void free_outgoing(struct outgoing
*o
)
126 static int apply_outgoing(struct outgoing
*o
, char *fn
, FILE *f
)
131 struct ast_variable
*var
, *last
= o
->vars
;
133 while (last
&& last
->next
) {
137 while(fgets(buf
, sizeof(buf
), f
)) {
141 while ((c
= strchr(c
, '#'))) {
142 if ((c
== buf
) || (*(c
-1) == ' ') || (*(c
-1) == '\t'))
149 while ((c
= strchr(c
, ';'))) {
150 if ((c
> buf
) && (c
[-1] == '\\')) {
151 memmove(c
- 1, c
, strlen(c
) + 1);
159 /* Trim trailing white space */
160 while(!ast_strlen_zero(buf
) && buf
[strlen(buf
) - 1] < 33)
161 buf
[strlen(buf
) - 1] = '\0';
162 if (!ast_strlen_zero(buf
)) {
163 c
= strchr(buf
, ':');
167 while ((*c
) && (*c
< 33))
170 printf("'%s' is '%s' at line %d\n", buf
, c
, lineno
);
172 if (!strcasecmp(buf
, "channel")) {
173 ast_copy_string(o
->tech
, c
, sizeof(o
->tech
));
174 if ((c2
= strchr(o
->tech
, '/'))) {
177 ast_copy_string(o
->dest
, c2
, sizeof(o
->dest
));
179 ast_log(LOG_NOTICE
, "Channel should be in form Tech/Dest at line %d of %s\n", lineno
, fn
);
182 } else if (!strcasecmp(buf
, "callerid")) {
183 ast_callerid_split(c
, o
->cid_name
, sizeof(o
->cid_name
), o
->cid_num
, sizeof(o
->cid_num
));
184 } else if (!strcasecmp(buf
, "application")) {
185 ast_copy_string(o
->app
, c
, sizeof(o
->app
));
186 } else if (!strcasecmp(buf
, "data")) {
187 ast_copy_string(o
->data
, c
, sizeof(o
->data
));
188 } else if (!strcasecmp(buf
, "maxretries")) {
189 if (sscanf(c
, "%d", &o
->maxretries
) != 1) {
190 ast_log(LOG_WARNING
, "Invalid max retries at line %d of %s\n", lineno
, fn
);
193 } else if (!strcasecmp(buf
, "context")) {
194 ast_copy_string(o
->context
, c
, sizeof(o
->context
));
195 } else if (!strcasecmp(buf
, "extension")) {
196 ast_copy_string(o
->exten
, c
, sizeof(o
->exten
));
197 } else if (!strcasecmp(buf
, "priority")) {
198 if ((sscanf(c
, "%d", &o
->priority
) != 1) || (o
->priority
< 1)) {
199 ast_log(LOG_WARNING
, "Invalid priority at line %d of %s\n", lineno
, fn
);
202 } else if (!strcasecmp(buf
, "retrytime")) {
203 if ((sscanf(c
, "%d", &o
->retrytime
) != 1) || (o
->retrytime
< 1)) {
204 ast_log(LOG_WARNING
, "Invalid retrytime at line %d of %s\n", lineno
, fn
);
207 } else if (!strcasecmp(buf
, "waittime")) {
208 if ((sscanf(c
, "%d", &o
->waittime
) != 1) || (o
->waittime
< 1)) {
209 ast_log(LOG_WARNING
, "Invalid waittime at line %d of %s\n", lineno
, fn
);
212 } else if (!strcasecmp(buf
, "retry")) {
214 } else if (!strcasecmp(buf
, "startretry")) {
215 if (sscanf(c
, "%ld", &o
->callingpid
) != 1) {
216 ast_log(LOG_WARNING
, "Unable to retrieve calling PID!\n");
219 } else if (!strcasecmp(buf
, "endretry") || !strcasecmp(buf
, "abortretry")) {
222 } else if (!strcasecmp(buf
, "delayedretry")) {
223 } else if (!strcasecmp(buf
, "setvar") || !strcasecmp(buf
, "set")) {
227 var
= ast_variable_new(c
, c2
);
229 /* Always insert at the end, because some people want to treat the spool file as a script */
238 ast_log(LOG_WARNING
, "Malformed \"%s\" argument. Should be \"%s: variable=value\"\n", buf
, buf
);
239 } else if (!strcasecmp(buf
, "account")) {
240 ast_copy_string(o
->account
, c
, sizeof(o
->account
));
241 } else if (!strcasecmp(buf
, "alwaysdelete")) {
242 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ALWAYS_DELETE
);
243 } else if (!strcasecmp(buf
, "archive")) {
244 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ARCHIVE
);
246 ast_log(LOG_WARNING
, "Unknown keyword '%s' at line %d of %s\n", buf
, lineno
, fn
);
249 ast_log(LOG_NOTICE
, "Syntax error at line %d of %s\n", lineno
, fn
);
252 ast_copy_string(o
->fn
, fn
, sizeof(o
->fn
));
253 if (ast_strlen_zero(o
->tech
) || ast_strlen_zero(o
->dest
) || (ast_strlen_zero(o
->app
) && ast_strlen_zero(o
->exten
))) {
254 ast_log(LOG_WARNING
, "At least one of app or extension must be specified, along with tech and dest in file %s\n", fn
);
260 static void safe_append(struct outgoing
*o
, time_t now
, char *s
)
265 fd
= open(o
->fn
, O_WRONLY
|O_APPEND
);
269 fprintf(f
, "\n%s: %ld %d (%ld)\n", s
, (long)ast_mainpid
, o
->retries
, (long) now
);
273 /* Update the file time */
275 tbuf
.modtime
= now
+ o
->retrytime
;
276 if (utime(o
->fn
, &tbuf
))
277 ast_log(LOG_WARNING
, "Unable to set utime on %s: %s\n", o
->fn
, strerror(errno
));
282 * \brief Remove a call file from the outgoing queue optionally moving it in the archive dir
284 * \param o the pointer to outgoing struct
285 * \param status the exit status of the call. Can be "Completed", "Failed" or "Expired"
287 static int remove_from_queue(struct outgoing
*o
, const char *status
)
294 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ALWAYS_DELETE
)) {
295 struct stat current_file_status
;
297 if (!stat(o
->fn
, ¤t_file_status
))
298 if (time(NULL
) < current_file_status
.st_mtime
)
302 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ARCHIVE
)) {
306 if (mkdir(qdonedir
, 0700) && (errno
!= EEXIST
)) {
307 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool archiving disabled\n", qdonedir
);
311 fd
= open(o
->fn
, O_WRONLY
|O_APPEND
);
315 fprintf(f
, "Status: %s\n", status
);
321 bname
= strrchr(o
->fn
,'/');
326 snprintf(newfn
, sizeof(newfn
), "%s/%s", qdonedir
, bname
);
327 /* a existing call file the archive dir is overwritten */
329 if (rename(o
->fn
, newfn
) != 0) {
336 static void *attempt_thread(void *data
)
338 struct outgoing
*o
= data
;
340 if (!ast_strlen_zero(o
->app
)) {
341 if (option_verbose
> 2)
342 ast_verbose(VERBOSE_PREFIX_3
"Attempting call on %s/%s for application %s(%s) (Retry %d)\n", o
->tech
, o
->dest
, o
->app
, o
->data
, o
->retries
);
343 res
= ast_pbx_outgoing_app(o
->tech
, AST_FORMAT_SLINEAR
, 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
);
345 if (option_verbose
> 2)
346 ast_verbose(VERBOSE_PREFIX_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
);
347 res
= ast_pbx_outgoing_exten(o
->tech
, AST_FORMAT_SLINEAR
, 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
);
350 ast_log(LOG_NOTICE
, "Call failed to go through, reason (%d) %s\n", reason
, ast_channel_reason2str(reason
));
351 if (o
->retries
>= o
->maxretries
+ 1) {
352 /* Max retries exceeded */
353 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" : "");
354 remove_from_queue(o
, "Expired");
356 /* Notate that the call is still active */
357 safe_append(o
, time(NULL
), "EndRetry");
360 ast_log(LOG_NOTICE
, "Call completed to %s/%s\n", o
->tech
, o
->dest
);
361 ast_log(LOG_EVENT
, "Queued call to %s/%s completed\n", o
->tech
, o
->dest
);
362 remove_from_queue(o
, "Completed");
368 static void launch_service(struct outgoing
*o
)
373 pthread_attr_init(&attr
);
374 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
375 if ((ret
= ast_pthread_create(&t
,&attr
,attempt_thread
, o
)) != 0) {
376 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
379 pthread_attr_destroy(&attr
);
382 static int scan_service(char *fn
, time_t now
, time_t atime
)
386 o
= malloc(sizeof(struct outgoing
));
391 if (!apply_outgoing(o
, fn
, f
)) {
393 printf("Filename: %s, Retries: %d, max: %d\n", fn
, o
->retries
, o
->maxretries
);
396 if (o
->retries
<= o
->maxretries
) {
398 if (o
->callingpid
&& (o
->callingpid
== ast_mainpid
)) {
399 safe_append(o
, time(NULL
), "DelayedRetry");
400 ast_log(LOG_DEBUG
, "Delaying retry since we're currently running '%s'\n", o
->fn
);
403 /* Increment retries */
405 /* If someone else was calling, they're presumably gone now
406 so abort their retry and continue as we were... */
408 safe_append(o
, time(NULL
), "AbortRetry");
410 safe_append(o
, now
, "StartRetry");
415 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" : "");
417 remove_from_queue(o
, "Expired");
422 ast_log(LOG_WARNING
, "Invalid file contents in %s, deleting\n", fn
);
424 remove_from_queue(o
, "Failed");
428 ast_log(LOG_WARNING
, "Unable to open %s: %s, deleting\n", fn
, strerror(errno
));
429 remove_from_queue(o
, "Failed");
432 ast_log(LOG_WARNING
, "Out of memory :(\n");
436 static void *scan_thread(void *unused
)
443 time_t last
= 0, next
= 0, now
;
448 if (!stat(qdir
, &st
)) {
449 if ((st
.st_mtime
!= last
) || (next
&& (now
> next
))) {
451 printf("atime: %ld, mtime: %ld, ctime: %ld\n", st
.st_atime
, st
.st_mtime
, st
.st_ctime
);
452 printf("Ooh, something changed / timeout\n");
458 while((de
= readdir(dir
))) {
459 snprintf(fn
, sizeof(fn
), "%s/%s", qdir
, de
->d_name
);
460 if (!stat(fn
, &st
)) {
461 if (S_ISREG(st
.st_mode
)) {
462 if (st
.st_mtime
<= now
) {
463 res
= scan_service(fn
, now
, st
.st_atime
);
465 /* Update next service time */
466 if (!next
|| (res
< next
)) {
470 ast_log(LOG_WARNING
, "Failed to scan service '%s'\n", fn
);
472 /* Update "next" update if necessary */
473 if (!next
|| (st
.st_mtime
< next
))
478 ast_log(LOG_WARNING
, "Unable to stat %s: %s\n", fn
, strerror(errno
));
482 ast_log(LOG_WARNING
, "Unable to open directory %s: %s\n", qdir
, strerror(errno
));
485 ast_log(LOG_WARNING
, "Unable to stat %s\n", qdir
);
490 static int unload_module(void)
495 static int load_module(void)
500 snprintf(qdir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing");
501 if (mkdir(qdir
, 0700) && (errno
!= EEXIST
)) {
502 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool disabled\n", qdir
);
505 snprintf(qdonedir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing_done");
506 pthread_attr_init(&attr
);
507 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
508 if ((ret
= ast_pthread_create_background(&thread
,&attr
,scan_thread
, NULL
)) != 0) {
509 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
512 pthread_attr_destroy(&attr
);
516 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Outgoing Spool Support");