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
;
133 while(fgets(buf
, sizeof(buf
), f
)) {
137 while ((c
= strchr(c
, '#'))) {
138 if ((c
== buf
) || (*(c
-1) == ' ') || (*(c
-1) == '\t'))
145 while ((c
= strchr(c
, ';'))) {
146 if ((c
> buf
) && (c
[-1] == '\\')) {
147 memmove(c
- 1, c
, strlen(c
) + 1);
155 /* Trim trailing white space */
156 while(!ast_strlen_zero(buf
) && buf
[strlen(buf
) - 1] < 33)
157 buf
[strlen(buf
) - 1] = '\0';
158 if (!ast_strlen_zero(buf
)) {
159 c
= strchr(buf
, ':');
163 while ((*c
) && (*c
< 33))
166 printf("'%s' is '%s' at line %d\n", buf
, c
, lineno
);
168 if (!strcasecmp(buf
, "channel")) {
169 ast_copy_string(o
->tech
, c
, sizeof(o
->tech
));
170 if ((c2
= strchr(o
->tech
, '/'))) {
173 ast_copy_string(o
->dest
, c2
, sizeof(o
->dest
));
175 ast_log(LOG_NOTICE
, "Channel should be in form Tech/Dest at line %d of %s\n", lineno
, fn
);
178 } else if (!strcasecmp(buf
, "callerid")) {
179 ast_callerid_split(c
, o
->cid_name
, sizeof(o
->cid_name
), o
->cid_num
, sizeof(o
->cid_num
));
180 } else if (!strcasecmp(buf
, "application")) {
181 ast_copy_string(o
->app
, c
, sizeof(o
->app
));
182 } else if (!strcasecmp(buf
, "data")) {
183 ast_copy_string(o
->data
, c
, sizeof(o
->data
));
184 } else if (!strcasecmp(buf
, "maxretries")) {
185 if (sscanf(c
, "%d", &o
->maxretries
) != 1) {
186 ast_log(LOG_WARNING
, "Invalid max retries at line %d of %s\n", lineno
, fn
);
189 } else if (!strcasecmp(buf
, "context")) {
190 ast_copy_string(o
->context
, c
, sizeof(o
->context
));
191 } else if (!strcasecmp(buf
, "extension")) {
192 ast_copy_string(o
->exten
, c
, sizeof(o
->exten
));
193 } else if (!strcasecmp(buf
, "priority")) {
194 if ((sscanf(c
, "%d", &o
->priority
) != 1) || (o
->priority
< 1)) {
195 ast_log(LOG_WARNING
, "Invalid priority at line %d of %s\n", lineno
, fn
);
198 } else if (!strcasecmp(buf
, "retrytime")) {
199 if ((sscanf(c
, "%d", &o
->retrytime
) != 1) || (o
->retrytime
< 1)) {
200 ast_log(LOG_WARNING
, "Invalid retrytime at line %d of %s\n", lineno
, fn
);
203 } else if (!strcasecmp(buf
, "waittime")) {
204 if ((sscanf(c
, "%d", &o
->waittime
) != 1) || (o
->waittime
< 1)) {
205 ast_log(LOG_WARNING
, "Invalid waittime at line %d of %s\n", lineno
, fn
);
208 } else if (!strcasecmp(buf
, "retry")) {
210 } else if (!strcasecmp(buf
, "startretry")) {
211 if (sscanf(c
, "%ld", &o
->callingpid
) != 1) {
212 ast_log(LOG_WARNING
, "Unable to retrieve calling PID!\n");
215 } else if (!strcasecmp(buf
, "endretry") || !strcasecmp(buf
, "abortretry")) {
218 } else if (!strcasecmp(buf
, "delayedretry")) {
219 } else if (!strcasecmp(buf
, "setvar") || !strcasecmp(buf
, "set")) {
223 var
= ast_variable_new(c
, c2
);
229 ast_log(LOG_WARNING
, "Malformed \"%s\" argument. Should be \"%s: variable=value\"\n", buf
, buf
);
230 } else if (!strcasecmp(buf
, "account")) {
231 ast_copy_string(o
->account
, c
, sizeof(o
->account
));
232 } else if (!strcasecmp(buf
, "alwaysdelete")) {
233 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ALWAYS_DELETE
);
234 } else if (!strcasecmp(buf
, "archive")) {
235 ast_set2_flag(&o
->options
, ast_true(c
), SPOOL_FLAG_ARCHIVE
);
237 ast_log(LOG_WARNING
, "Unknown keyword '%s' at line %d of %s\n", buf
, lineno
, fn
);
240 ast_log(LOG_NOTICE
, "Syntax error at line %d of %s\n", lineno
, fn
);
243 ast_copy_string(o
->fn
, fn
, sizeof(o
->fn
));
244 if (ast_strlen_zero(o
->tech
) || ast_strlen_zero(o
->dest
) || (ast_strlen_zero(o
->app
) && ast_strlen_zero(o
->exten
))) {
245 ast_log(LOG_WARNING
, "At least one of app or extension must be specified, along with tech and dest in file %s\n", fn
);
251 static void safe_append(struct outgoing
*o
, time_t now
, char *s
)
256 fd
= open(o
->fn
, O_WRONLY
|O_APPEND
);
260 fprintf(f
, "\n%s: %ld %d (%ld)\n", s
, (long)ast_mainpid
, o
->retries
, (long) now
);
264 /* Update the file time */
266 tbuf
.modtime
= now
+ o
->retrytime
;
267 if (utime(o
->fn
, &tbuf
))
268 ast_log(LOG_WARNING
, "Unable to set utime on %s: %s\n", o
->fn
, strerror(errno
));
273 * \brief Remove a call file from the outgoing queue optionally moving it in the archive dir
275 * \param o the pointer to outgoing struct
276 * \param status the exit status of the call. Can be "Completed", "Failed" or "Expired"
278 static int remove_from_queue(struct outgoing
*o
, const char *status
)
285 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ALWAYS_DELETE
)) {
286 struct stat current_file_status
;
288 if (!stat(o
->fn
, ¤t_file_status
))
289 if (time(NULL
) < current_file_status
.st_mtime
)
293 if (!ast_test_flag(&o
->options
, SPOOL_FLAG_ARCHIVE
)) {
297 if (mkdir(qdonedir
, 0700) && (errno
!= EEXIST
)) {
298 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool archiving disabled\n", qdonedir
);
302 fd
= open(o
->fn
, O_WRONLY
|O_APPEND
);
306 fprintf(f
, "Status: %s\n", status
);
312 bname
= strrchr(o
->fn
,'/');
317 snprintf(newfn
, sizeof(newfn
), "%s/%s", qdonedir
, bname
);
318 /* a existing call file the archive dir is overwritten */
320 if (rename(o
->fn
, newfn
) != 0) {
327 static void *attempt_thread(void *data
)
329 struct outgoing
*o
= data
;
331 if (!ast_strlen_zero(o
->app
)) {
332 if (option_verbose
> 2)
333 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
);
334 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
);
336 if (option_verbose
> 2)
337 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
);
338 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
);
341 ast_log(LOG_NOTICE
, "Call failed to go through, reason (%d) %s\n", reason
, ast_channel_reason2str(reason
));
342 if (o
->retries
>= o
->maxretries
+ 1) {
343 /* Max retries exceeded */
344 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" : "");
345 remove_from_queue(o
, "Expired");
347 /* Notate that the call is still active */
348 safe_append(o
, time(NULL
), "EndRetry");
351 ast_log(LOG_NOTICE
, "Call completed to %s/%s\n", o
->tech
, o
->dest
);
352 ast_log(LOG_EVENT
, "Queued call to %s/%s completed\n", o
->tech
, o
->dest
);
353 remove_from_queue(o
, "Completed");
359 static void launch_service(struct outgoing
*o
)
364 pthread_attr_init(&attr
);
365 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
366 if ((ret
= ast_pthread_create(&t
,&attr
,attempt_thread
, o
)) != 0) {
367 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
370 pthread_attr_destroy(&attr
);
373 static int scan_service(char *fn
, time_t now
, time_t atime
)
377 o
= malloc(sizeof(struct outgoing
));
382 if (!apply_outgoing(o
, fn
, f
)) {
384 printf("Filename: %s, Retries: %d, max: %d\n", fn
, o
->retries
, o
->maxretries
);
387 if (o
->retries
<= o
->maxretries
) {
389 if (o
->callingpid
&& (o
->callingpid
== ast_mainpid
)) {
390 safe_append(o
, time(NULL
), "DelayedRetry");
391 ast_log(LOG_DEBUG
, "Delaying retry since we're currently running '%s'\n", o
->fn
);
394 /* Increment retries */
396 /* If someone else was calling, they're presumably gone now
397 so abort their retry and continue as we were... */
399 safe_append(o
, time(NULL
), "AbortRetry");
401 safe_append(o
, now
, "StartRetry");
406 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" : "");
408 remove_from_queue(o
, "Expired");
413 ast_log(LOG_WARNING
, "Invalid file contents in %s, deleting\n", fn
);
415 remove_from_queue(o
, "Failed");
419 ast_log(LOG_WARNING
, "Unable to open %s: %s, deleting\n", fn
, strerror(errno
));
420 remove_from_queue(o
, "Failed");
423 ast_log(LOG_WARNING
, "Out of memory :(\n");
427 static void *scan_thread(void *unused
)
434 time_t last
= 0, next
= 0, now
;
439 if (!stat(qdir
, &st
)) {
440 if ((st
.st_mtime
!= last
) || (next
&& (now
> next
))) {
442 printf("atime: %ld, mtime: %ld, ctime: %ld\n", st
.st_atime
, st
.st_mtime
, st
.st_ctime
);
443 printf("Ooh, something changed / timeout\n");
449 while((de
= readdir(dir
))) {
450 snprintf(fn
, sizeof(fn
), "%s/%s", qdir
, de
->d_name
);
451 if (!stat(fn
, &st
)) {
452 if (S_ISREG(st
.st_mode
)) {
453 if (st
.st_mtime
<= now
) {
454 res
= scan_service(fn
, now
, st
.st_atime
);
456 /* Update next service time */
457 if (!next
|| (res
< next
)) {
461 ast_log(LOG_WARNING
, "Failed to scan service '%s'\n", fn
);
463 /* Update "next" update if necessary */
464 if (!next
|| (st
.st_mtime
< next
))
469 ast_log(LOG_WARNING
, "Unable to stat %s: %s\n", fn
, strerror(errno
));
473 ast_log(LOG_WARNING
, "Unable to open directory %s: %s\n", qdir
, strerror(errno
));
476 ast_log(LOG_WARNING
, "Unable to stat %s\n", qdir
);
481 static int unload_module(void)
486 static int load_module(void)
491 snprintf(qdir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing");
492 if (mkdir(qdir
, 0700) && (errno
!= EEXIST
)) {
493 ast_log(LOG_WARNING
, "Unable to create queue directory %s -- outgoing spool disabled\n", qdir
);
496 snprintf(qdonedir
, sizeof(qdir
), "%s/%s", ast_config_AST_SPOOL_DIR
, "outgoing_done");
497 pthread_attr_init(&attr
);
498 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
499 if ((ret
= ast_pthread_create_background(&thread
,&attr
,scan_thread
, NULL
)) != 0) {
500 ast_log(LOG_WARNING
, "Unable to create thread :( (returned error: %d)\n", ret
);
503 pthread_attr_destroy(&attr
);
507 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Outgoing Spool Support");