36 #include <sys/inotify.h>
37 #elif defined(HAVE_KQUEUE)
38 #include <sys/types.h>
40 #include <sys/event.h>
69 SPOOL_FLAG_ARCHIVE = (1 << 1),
71 SPOOL_FLAG_EARLY_MEDIA = (1 << 2),
74 static char qdir[255];
75 static char qdonedir[255];
102 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
111 static void queue_file(
const char *filename, time_t when);
114 static void free_outgoing(
struct outgoing *o)
124 static struct outgoing *new_outgoing(
const char *
fn)
148 if (ast_strlen_zero(o->
fn)) {
164 static void append_variable(
struct outgoing *o,
const char *name,
const char *value)
174 ast_variable_list_append(&o->
vars, var);
177 static void parse_line(
char *line,
unsigned int lineno,
struct outgoing *o)
183 while ((c = strchr(c,
'#'))) {
184 if ((c == line) || (*(c-1) ==
' ') || (*(c-1) ==
'\t')) {
192 while ((c = strchr(c,
';'))) {
193 if ((c > line) && (c[-1] ==
'\\')) {
194 memmove(c - 1, c, strlen(c) + 1);
203 if (ast_strlen_zero(line)) {
206 c = strchr(line,
':');
208 ast_log(LOG_NOTICE,
"Syntax error at line %d of %s\n", lineno, o->
fn);
214 printf(
"'%s' is '%s' at line %d\n", line, c, lineno);
216 if (!strcasecmp(line,
"channel")) {
218 if ((c2 = strchr(c,
'/'))) {
224 ast_log(LOG_NOTICE,
"Channel should be in form Tech/Dest at line %d of %s\n", lineno, o->
fn);
226 }
else if (!strcasecmp(line,
"callerid")) {
227 char cid_name[80] = {0}, cid_num[80] = {0};
228 ast_callerid_split(c, cid_name,
sizeof(cid_name), cid_num,
sizeof(cid_num));
231 }
else if (!strcasecmp(line,
"application")) {
233 }
else if (!strcasecmp(line,
"data")) {
235 }
else if (!strcasecmp(line,
"maxretries")) {
237 ast_log(LOG_WARNING,
"Invalid max retries at line %d of %s\n", lineno, o->
fn);
240 }
else if (!strcasecmp(line,
"codecs")) {
242 }
else if (!strcasecmp(line,
"context")) {
244 }
else if (!strcasecmp(line,
"extension")) {
246 }
else if (!strcasecmp(line,
"priority")) {
248 ast_log(LOG_WARNING,
"Invalid priority at line %d of %s\n", lineno, o->
fn);
251 }
else if (!strcasecmp(line,
"retrytime")) {
253 ast_log(LOG_WARNING,
"Invalid retrytime at line %d of %s\n", lineno, o->
fn);
256 }
else if (!strcasecmp(line,
"waittime")) {
258 ast_log(LOG_WARNING,
"Invalid waittime at line %d of %s\n", lineno, o->
fn);
261 }
else if (!strcasecmp(line,
"retry")) {
263 }
else if (!strcasecmp(line,
"startretry")) {
264 if (sscanf(c,
"%30ld", &o->
callingpid) != 1) {
265 ast_log(LOG_WARNING,
"Unable to retrieve calling PID!\n");
268 }
else if (!strcasecmp(line,
"endretry") || !strcasecmp(line,
"abortretry")) {
271 }
else if (!strcasecmp(line,
"delayedretry")) {
272 }
else if (!strcasecmp(line,
"setvar") || !strcasecmp(line,
"set")) {
277 append_variable(o, c, c2);
279 ast_log(LOG_WARNING,
"Malformed \"%s\" argument. Should be \"%s: variable=value\"\n", line, line);
281 }
else if (!strcasecmp(line,
"account")) {
283 }
else if (!strcasecmp(line,
"alwaysdelete")) {
285 }
else if (!strcasecmp(line,
"archive")) {
287 }
else if (!strcasecmp(line,
"early_media")) {
290 ast_log(LOG_WARNING,
"Unknown keyword '%s' at line %d of %s\n", line, lineno, o->
fn);
294 #define LINE_BUFFER_SIZE 1024
296 static int apply_outgoing(
struct outgoing *o, FILE *f)
298 char buf[LINE_BUFFER_SIZE];
299 unsigned int lineno = 0;
301 while (fgets(buf,
sizeof(buf), f)) {
302 size_t len = strlen(buf);
306 if (buf[len - 1] ==
'\n' || feof(f)) {
308 parse_line(buf, lineno, o);
313 ast_log(LOG_WARNING,
"Skipping extremely long line at line %d of %s\n", lineno, o->
fn);
316 while (fgets(buf,
sizeof(buf), f)) {
318 if (buf[len - 1] ==
'\n' || feof(f)) {
324 if (ast_strlen_zero(o->
tech)
325 || ast_strlen_zero(o->
dest)
326 || (ast_strlen_zero(o->
app) && ast_strlen_zero(o->
exten))) {
327 ast_log(LOG_WARNING,
"At least one of app or extension must be specified, "
328 "along with tech and dest in file %s\n", o->
fn);
332 if (snprintf(buf,
sizeof(buf),
"%d", o->
retries + 1) <
sizeof(buf)) {
333 append_variable(o,
"AST_OUTGOING_ATTEMPT", buf);
339 static void safe_append(
struct outgoing *o, time_t now,
char *s)
342 struct utimbuf tbuf = { .actime = now, .modtime = now + o->
retrytime };
346 if ((f = fopen(o->
fn,
"a"))) {
347 fprintf(f,
"\n%s: %ld %d (%ld)\n", s, (
long)ast_mainpid, o->
retries, (
long) now);
352 if (utime(o->
fn, &tbuf)) {
353 ast_log(LOG_WARNING,
"Unable to set utime on %s: %s\n", o->
fn, strerror(errno));
369 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
374 struct stat current_file_status;
376 if (!stat(o->
fn, ¤t_file_status)) {
377 if (time(NULL) < current_file_status.st_mtime) {
383 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
386 if (!strcmp(cur->name, o->
fn)) {
396 if (!ast_test_flag(&o->
options, SPOOL_FLAG_ARCHIVE)) {
402 ast_log(LOG_WARNING,
"Unable to create queue directory %s -- outgoing spool archiving disabled\n", qdonedir);
407 if (!(bname = strrchr(o->
fn,
'/'))) {
413 snprintf(newfn,
sizeof(newfn),
"%s/%s", qdonedir, bname);
416 if (rename(o->
fn, newfn) != 0) {
423 if ((f = fopen(newfn,
"a"))) {
424 fprintf(f,
"Status: %s\n", status);
431 static void *attempt_thread(
void *data)
435 if (!ast_strlen_zero(o->
app)) {
436 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);
450 ast_log(LOG_NOTICE,
"Call failed to go through, reason (%d) %s\n", reason,
ast_channel_reason2str(reason));
453 ast_log(LOG_NOTICE,
"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" :
"");
457 safe_append(o, time(NULL),
"EndRetry");
458 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
463 ast_log(LOG_NOTICE,
"Call completed to %s/%s\n", o->
tech, o->
dest);
470 static void launch_service(
struct outgoing *o)
475 if ((ret = ast_pthread_create_detached(&t, NULL, attempt_thread, o))) {
476 ast_log(LOG_WARNING,
"Unable to create thread :( (returned error: %d)\n", ret);
488 o = new_outgoing(fn);
494 f = fopen(o->
fn,
"r");
496 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
505 ast_log(LOG_WARNING,
"Unable to open %s: '%s'(%d), deleting\n",
506 o->
fn, strerror(errno), (
int) errno);
514 res = apply_outgoing(o, f);
517 ast_log(LOG_WARNING,
"Invalid file contents in %s, deleting\n", o->
fn);
527 safe_append(o, time(NULL),
"DelayedRetry");
528 ast_debug(1,
"Delaying retry since we're currently running '%s'\n", o->
fn);
536 safe_append(o, time(NULL),
"AbortRetry");
538 safe_append(o, now,
"StartRetry");
544 ast_log(LOG_NOTICE,
"Queued call to %s/%s expired without completion after %d attempt%s\n",
552 #if defined(HAVE_INOTIFY)
558 #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE)
560 static void queue_file(
const char *filename, time_t when)
565 time_t now = time(NULL);
567 if (!strchr(filename,
'/')) {
568 char *fn =
ast_alloca(strlen(qdir) + strlen(filename) + 2);
569 sprintf(fn,
"%s/%s", qdir, filename);
574 if (stat(filename, &st)) {
575 ast_log(LOG_WARNING,
"Unable to stat %s: %s\n", filename, strerror(errno));
579 if (!S_ISREG(st.st_mode)) {
589 if (cur->mtime == when && !strcmp(filename, cur->name)) {
595 if ((res = when) > now || (res =
scan_service(filename, now)) > 0) {
596 if (!(
new =
ast_calloc(1,
sizeof(*
new) + strlen(filename) + 1))) {
601 strcpy(new->name, filename);
608 if (cur->mtime > new->mtime) {
624 static void queue_file_create(
const char *filename)
629 if (!strcmp(cur->name, filename)) {
634 if (!(cur =
ast_calloc(1,
sizeof(*cur) + strlen(filename) + 1))) {
637 strcpy(cur->name, filename);
639 cur->mtime = time(NULL) + 2;
643 static void queue_file_open(
const char *filename)
648 if (!strcmp(cur->name, filename)) {
657 static void queue_created_files(
void)
660 time_t now = time(NULL);
663 if (cur->mtime > now) {
668 queue_file(cur->name, 0);
674 static void queue_file_write(
const char *filename)
679 if (!strcmp(cur->name, filename)) {
682 queue_file(filename, 0);
690 static void *scan_thread(
void *unused)
695 struct timespec ts = { .tv_sec = 1 };
698 int inotify_fd = inotify_init();
699 struct inotify_event *iev;
700 char buf[8192] __attribute__((aligned (
sizeof(
int))));
701 struct pollfd pfd = { .fd = inotify_fd, .events = POLLIN };
703 struct timespec nowait = { .tv_sec = 0, .tv_nsec = 1 };
704 int inotify_fd = kqueue();
710 while (!ast_fully_booted) {
711 nanosleep(&ts, NULL);
714 if (inotify_fd < 0) {
715 ast_log(LOG_ERROR,
"Unable to initialize "
726 inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_OPEN | IN_CLOSE_WRITE | IN_MOVED_TO);
730 if (!(dir = opendir(qdir))) {
731 ast_log(LOG_ERROR,
"Unable to open directory %s: %s\n", qdir, strerror(errno));
736 EV_SET(&kev, dirfd(dir), EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE, 0, NULL);
737 if (kevent(inotify_fd, &kev, 1, &
event, 1, &nowait) < 0 && errno != 0) {
738 ast_log(LOG_ERROR,
"Unable to watch directory %s: %s\n", qdir, strerror(errno));
742 while ((de = readdir(dir))) {
743 queue_file(de->d_name, 0);
761 int waittime = next == INT_MAX ? -1 : (next - now) * 1000;
766 if ((res = poll(&pfd, 1, waittime)) > 0 && (stage = 1) &&
767 (res = read(inotify_fd, &buf,
sizeof(buf))) >=
sizeof(*iev)) {
770 for (iev = (
void *) buf; res >=
sizeof(*iev); iev = (
struct inotify_event *) (((
char *) iev) + len)) {
787 if (iev->mask & IN_CREATE) {
788 queue_file_create(iev->name);
789 }
else if (iev->mask & IN_OPEN) {
790 queue_file_open(iev->name);
791 }
else if (iev->mask & IN_CLOSE_WRITE) {
792 queue_file_write(iev->name);
793 }
else if (iev->mask & IN_MOVED_TO) {
794 queue_file(iev->name, 0);
796 ast_log(LOG_ERROR,
"Unexpected event %d for file '%s'\n", (
int) iev->mask, iev->name);
799 len =
sizeof(*iev) + iev->len;
802 }
else if (res < 0 && errno != EINTR && errno != EAGAIN) {
803 ast_debug(1,
"Got an error back from %s(2): %s\n", stage ?
"read" :
"poll", strerror(errno));
807 queue_created_files();
811 if (next == INT_MAX) {
812 num_events = kevent(inotify_fd, &kev, 1, &
event, 1, NULL);
814 struct timespec ts2 = { .tv_sec = (
unsigned long int)(next - now), .tv_nsec = 0 };
815 num_events = kevent(inotify_fd, &kev, 1, &
event, 1, &ts2);
817 if ((num_events < 0) || (
event.flags == EV_ERROR)) {
818 ast_debug(10,
"KEvent error %s\n", strerror(errno));
820 }
else if (num_events == 0) {
826 while ((de = readdir(dir))) {
827 queue_file(de->d_name, 0);
838 queue_file(cur->name, cur->mtime);
847 static void *scan_thread(
void *unused)
858 struct timespec ts = { .tv_sec = 1 };
860 while (!ast_fully_booted) {
861 nanosleep(&ts, NULL);
866 nanosleep(&ts, NULL);
869 if (stat(qdir, &st)) {
870 ast_log(LOG_WARNING,
"Unable to stat %s\n", qdir);
875 if (!force_poll && st.st_mtime == last && (!next || now < next)) {
884 printf(
"atime: %ld, mtime: %ld, ctime: %ld\n", st.st_atime, st.st_mtime, st.st_ctime);
885 printf(
"Ooh, something changed / timeout\n");
888 if (!(dir = opendir(qdir))) {
889 ast_log(LOG_WARNING,
"Unable to open directory %s: %s\n", qdir, strerror(errno));
899 force_poll = (st.st_mtime == now);
903 while ((de = readdir(dir))) {
904 snprintf(fn,
sizeof(fn),
"%s/%s", qdir, de->d_name);
906 ast_log(LOG_WARNING,
"Unable to stat %s: %s\n", fn, strerror(errno));
909 if (!S_ISREG(st.st_mode)) {
913 if (st.st_mtime <= now) {
917 if (!next || res < next) {
922 ast_log(LOG_WARNING,
"Failed to scan service '%s'\n", fn);
929 if (!next || st.st_mtime < next) {
941 static int unload_module(
void)
946 static int load_module(
void)
950 snprintf(qdir,
sizeof(qdir),
"%s/%s", ast_config_AST_SPOOL_DIR,
"outgoing");
952 ast_log(LOG_WARNING,
"Unable to create queue directory %s -- outgoing spool disabled\n", qdir);
955 snprintf(qdonedir,
sizeof(qdir),
"%s/%s", ast_config_AST_SPOOL_DIR,
"outgoing_done");
957 if ((ret = ast_pthread_create_detached_background(&thread, NULL, scan_thread, NULL))) {
958 ast_log(LOG_WARNING,
"Unable to create thread :( (returned error: %d)\n", ret);
static int scan_service(const char *fn, time_t now)
#define AST_LIST_LOCK(head)
Locks a list.
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
const ast_string_field cid_name
Time-related functions and macros.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
static int remove_from_queue(struct outgoing *o, const char *status)
Remove a call file from the outgoing queue optionally moving it in the archive dir.
const ast_string_field cid_num
Structure for variables, used for configurations and for channel variables.
const ast_string_field app
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
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_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *context, const char *exten, int priority, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel, int early_media, const struct ast_assigned_ids *assignedids)
Synchronously or asynchronously make an outbound call and send it to a particular extension...
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
const ast_string_field context
const ast_string_field exten
const ast_string_field data
General Asterisk PBX channel definitions.
Asterisk file paths, configured in asterisk.conf.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
struct ast_variable * vars
const ast_string_field account
#define AST_STRING_FIELD(name)
Declare a string field.
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Core PBX routines and definitions.
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
#define AST_LIST_HEAD_NOLOCK_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
int ast_pbx_outgoing_app(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *app, const char *appdata, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel, const struct ast_assigned_ids *assignedids)
Synchronously or asynchronously make an outbound call and execute an application on the channel...
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
#define ast_calloc(num, len)
A wrapper for calloc()
Module could not be loaded properly.
const ast_string_field fn
Support for logging to various files, console and syslog Configuration in file logger.conf.
Module has failed to load, may be in an inconsistent state.
Structure used to handle boolean flags.
const ast_string_field dest
const ast_string_field tech
struct ast_format_cap * capabilities
Options provided by main asterisk program.
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
const char * ast_channel_reason2str(int reason)
return an english explanation of the code returned thru __ast_request_and_dial's 'outstate' argument ...
#define AST_LIST_INSERT_BEFORE_CURRENT(elm, field)
Inserts a list entry before the current entry during a traversal.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.