diff --git a/bbs/alertpipe.c b/bbs/alertpipe.c index b8286fd..fa2e7fe 100644 --- a/bbs/alertpipe.c +++ b/bbs/alertpipe.c @@ -50,10 +50,20 @@ int bbs_alertpipe_read(int alert_pipe[2]) return 0; } -int bbs_alertpipe_create(int alert_pipe[2]) +int __bbs_alertpipe_create(int alert_pipe[2], const char *file, int line, const char *func) { /* Prefer eventfd to pipe since it's more efficient (only 1 fd needed, rather than 2) */ - int fd = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE); + int fd; + +#if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 + fd = __bbs_eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE, file, line, func); +#else + UNUSED(file); + UNUSED(line); + UNUSED(func); + fd = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE); +#endif + if (fd > -1) { alert_pipe[0] = alert_pipe[1] = fd; return 0; @@ -101,6 +111,7 @@ int bbs_alertpipe_poll(int alert_pipe[2], int ms) if (pfd.revents) { return 1; } + break; } return res; } diff --git a/bbs/fd.c b/bbs/fd.c index f32dbcf..2caabdd 100644 --- a/bbs/fd.c +++ b/bbs/fd.c @@ -198,7 +198,7 @@ static int bbs_fd_dump(int fd) LOG_FAILURE(); \ } -int __fdleak_open(const char *file, int line, const char *func, const char *path, int flags, ...) +int __bbs_open(const char *file, int line, const char *func, const char *path, int flags, ...) { int res; va_list ap; @@ -236,7 +236,7 @@ int __fdleak_open(const char *file, int line, const char *func, const char *path return res; } -int __fdleak_accept(int socket, struct sockaddr *address, socklen_t *address_len, +int __bbs_accept(int socket, struct sockaddr *address, socklen_t *address_len, const char *file, int line, const char *func) { int res = accept(socket, address, address_len); @@ -244,7 +244,7 @@ int __fdleak_accept(int socket, struct sockaddr *address, socklen_t *address_len return res; } -int __fdleak_pipe(int *fds, const char *file, int line, const char *func) +int __bbs_pipe(int *fds, const char *file, int line, const char *func) { int i, res = pipe(fds); if (res) { @@ -256,7 +256,7 @@ int __fdleak_pipe(int *fds, const char *file, int line, const char *func) return 0; } -int __fdleak_socketpair(int domain, int type, int protocol, int sv[2], +int __bbs_socketpair(int domain, int type, int protocol, int sv[2], const char *file, int line, const char *func) { int i, res = socketpair(domain, type, protocol, sv); @@ -269,14 +269,14 @@ int __fdleak_socketpair(int domain, int type, int protocol, int sv[2], return 0; } -int __fdleak_eventfd(unsigned int initval, int flags, const char *file, int line, const char *func) +int __bbs_eventfd(unsigned int initval, int flags, const char *file, int line, const char *func) { int res = eventfd(initval, flags); STORE_COMMON_HELPER(res, "eventfd", "{%d}", res); return res; } -int __fdleak_socket(int domain, int type, int protocol, const char *file, int line, const char *func) +int __bbs_socket(int domain, int type, int protocol, const char *file, int line, const char *func) { char sdomain[20], stype[20]; const char *sproto = NULL; @@ -322,7 +322,7 @@ int __fdleak_socket(int domain, int type, int protocol, const char *file, int li return res; } -int __fdleak_close(int fd, const char *file, int line, const char *func) +int __bbs_close(int fd, const char *file, int line, const char *func) { int res; @@ -339,7 +339,7 @@ int __fdleak_close(int fd, const char *file, int line, const char *func) return res; } -FILE *__fdleak_fopen(const char *path, const char *mode, const char *file, int line, const char *func) +FILE *__bbs_fopen(const char *path, const char *mode, const char *file, int line, const char *func) { FILE *res = fopen(path, mode); int fd; @@ -351,7 +351,7 @@ FILE *__fdleak_fopen(const char *path, const char *mode, const char *file, int l return res; } -int __fdleak_fclose(FILE *ptr) +int __bbs_fclose(FILE *ptr) { int fd, res; @@ -364,7 +364,7 @@ int __fdleak_fclose(FILE *ptr) return res; } -int __fdleak_dup2(int oldfd, int newfd, const char *file, int line, const char *func) +int __bbs_dup2(int oldfd, int newfd, const char *file, int line, const char *func) { int res = dup2(oldfd, newfd); /* On success, newfd will be closed automatically if it was already @@ -374,7 +374,7 @@ int __fdleak_dup2(int oldfd, int newfd, const char *file, int line, const char * return res; } -int __fdleak_dup(int oldfd, const char *file, int line, const char *func) +int __bbs_dup(int oldfd, const char *file, int line, const char *func) { int res = dup(oldfd); STORE_COMMON_HELPER(res, "dup2", "%d", oldfd); diff --git a/bbs/socket.c b/bbs/socket.c index 2d60909..373c822 100644 --- a/bbs/socket.c +++ b/bbs/socket.c @@ -53,7 +53,7 @@ extern int option_rebind; -int bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_t uid, gid_t gid) +int __bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_t uid, gid_t gid, const char *file, int line, const char *func) { struct sockaddr_un sunaddr; /* UNIX socket */ int res; @@ -64,7 +64,14 @@ int bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_ unlink(sockfile); /* Set up the UNIX domain socket. */ +#if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 + uds_socket = __bbs_socket(PF_LOCAL, SOCK_STREAM, 0, file, line, func); +#else + UNUSED(file); + UNUSED(line); + UNUSED(func); uds_socket = socket(PF_LOCAL, SOCK_STREAM, 0); +#endif if (uds_socket < 0) { bbs_error("Unable to create UNIX domain socket: %s\n", strerror(errno)); return -1; @@ -105,13 +112,20 @@ int bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_ return 0; } -int bbs_make_tcp_socket(int *sock, int port) +int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const char *func) { struct sockaddr_in sinaddr; /* Internet socket */ const int enable = 1; int res; +#if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 + *sock = __bbs_socket(AF_INET, SOCK_STREAM, 0, file, line, func); +#else + UNUSED(file); + UNUSED(line); + UNUSED(func); *sock = socket(AF_INET, SOCK_STREAM, 0); +#endif if (*sock < 0) { bbs_error("Unable to create TCP socket: %s\n", strerror(errno)); return -1; @@ -317,7 +331,7 @@ int bbs_resolve_hostname(const char *hostname, char *buf, size_t len) return 0; } -int bbs_tcp_connect(const char *hostname, int port) +int __bbs_tcp_connect(const char *hostname, int port, const char *file, int line, const char *func) { char ip[256]; int e; @@ -353,7 +367,16 @@ int bbs_tcp_connect(const char *hostname, int port) } else { continue; } + +#if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 + sfd = __bbs_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, file, line, func); +#else + UNUSED(file); + UNUSED(line); + UNUSED(func); sfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); +#endif + if (sfd == -1) { bbs_error("socket: %s\n", strerror(errno)); continue; diff --git a/bbs/string.c b/bbs/string.c index 0fe8894..1dd284a 100644 --- a/bbs/string.c +++ b/bbs/string.c @@ -352,7 +352,7 @@ int bbs_str_anyprint(const char *restrict s) return 0; } -void str_tolower(char *restrict s) +void bbs_str_tolower(char *restrict s) { while (*s) { *s = (char) tolower(*s); @@ -360,6 +360,14 @@ void str_tolower(char *restrict s) } } +void bbs_str_toupper(char *restrict s) +{ + while (*s) { + *s = (char) toupper(*s); + s++; + } +} + int skipn(char **str, char c, int n) { int count = 0; diff --git a/include/alertpipe.h b/include/alertpipe.h index f6a153a..00ade5a 100644 --- a/include/alertpipe.h +++ b/include/alertpipe.h @@ -28,7 +28,9 @@ ssize_t bbs_alertpipe_write(int alert_pipe[2]); int bbs_alertpipe_read(int alert_pipe[2]); /*! \brief Initialize an alertpipe */ -int bbs_alertpipe_create(int alert_pipe[2]); +#define bbs_alertpipe_create(alert_pipe) __bbs_alertpipe_create(alert_pipe, __FILE__, __LINE__, __func__) + +int __bbs_alertpipe_create(int alert_pipe[2], const char *file, int line, const char *func); /*! \brief Close an alertpipe */ int bbs_alertpipe_close(int alert_pipe[2]); diff --git a/include/bbs.h b/include/bbs.h index 0011cb0..8b15cfd 100644 --- a/include/bbs.h +++ b/include/bbs.h @@ -110,29 +110,29 @@ */ #if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 -#define open(a,...) __fdleak_open(__FILE__,__LINE__,__func__, a, __VA_ARGS__) -#define pipe(a) __fdleak_pipe(a, __FILE__,__LINE__,__func__) -#define socketpair(a,b,c,d) __fdleak_socketpair(a, b, c, d, __FILE__,__LINE__,__func__) -#define socket(a,b,c) __fdleak_socket(a, b, c, __FILE__,__LINE__,__func__) -#define accept(a,b,c) __fdleak_accept(a, b, c, __FILE__,__LINE__,__func__) -#define close(a) __fdleak_close(a, __FILE__,__LINE__,__func__) -#define fopen(a,b) __fdleak_fopen(a, b, __FILE__,__LINE__,__func__) -#define fclose(a) __fdleak_fclose(a) -#define dup2(a,b) __fdleak_dup2(a, b, __FILE__,__LINE__,__func__) -#define dup(a) __fdleak_dup(a, __FILE__,__LINE__,__func__) -#define eventfd(a,b) __fdleak_eventfd(a,b, __FILE__,__LINE__,__func__) - -int __fdleak_open(const char *file, int line, const char *func, const char *path, int flags, ...); -int __fdleak_pipe(int *fds, const char *file, int line, const char *func); -int __fdleak_socketpair(int domain, int type, int protocol, int sv[2], const char *file, int line, const char *func); -int __fdleak_socket(int domain, int type, int protocol, const char *file, int line, const char *func); -int __fdleak_accept(int socket, struct sockaddr *address, socklen_t *address_len, const char *file, int line, const char *func); -int __fdleak_eventfd(unsigned int initval, int flags, const char *file, int line, const char *func); -int __fdleak_close(int fd, const char *file, int line, const char *func); -FILE *__fdleak_fopen(const char *path, const char *mode, const char *file, int line, const char *func); -int __fdleak_fclose(FILE *ptr); -int __fdleak_dup2(int oldfd, int newfd, const char *file, int line, const char *func); -int __fdleak_dup(int oldfd, const char *file, int line, const char *func); +#define open(a,...) __bbs_open(__FILE__,__LINE__,__func__, a, __VA_ARGS__) +#define pipe(a) __bbs_pipe(a, __FILE__,__LINE__,__func__) +#define socketpair(a,b,c,d) __bbs_socketpair(a, b, c, d, __FILE__,__LINE__,__func__) +#define socket(a,b,c) __bbs_socket(a, b, c, __FILE__,__LINE__,__func__) +#define accept(a,b,c) __bbs_accept(a, b, c, __FILE__,__LINE__,__func__) +#define close(a) __bbs_close(a, __FILE__,__LINE__,__func__) +#define fopen(a,b) __bbs_fopen(a, b, __FILE__,__LINE__,__func__) +#define fclose(a) __bbs_fclose(a) +#define dup2(a,b) __bbs_dup2(a, b, __FILE__,__LINE__,__func__) +#define dup(a) __bbs_dup(a, __FILE__,__LINE__,__func__) +#define eventfd(a,b) __bbs_eventfd(a, b, __FILE__,__LINE__,__func__) + +int __bbs_open(const char *file, int line, const char *func, const char *path, int flags, ...); +int __bbs_pipe(int *fds, const char *file, int line, const char *func); +int __bbs_socketpair(int domain, int type, int protocol, int sv[2], const char *file, int line, const char *func); +int __bbs_socket(int domain, int type, int protocol, const char *file, int line, const char *func); +int __bbs_accept(int socket, struct sockaddr *address, socklen_t *address_len, const char *file, int line, const char *func); +int __bbs_eventfd(unsigned int initval, int flags, const char *file, int line, const char *func); +int __bbs_close(int fd, const char *file, int line, const char *func); +FILE *__bbs_fopen(const char *path, const char *mode, const char *file, int line, const char *func); +int __bbs_fclose(FILE *ptr); +int __bbs_dup2(int oldfd, int newfd, const char *file, int line, const char *func); +int __bbs_dup(int oldfd, const char *file, int line, const char *func); void bbs_fd_shutdown(void); int bbs_fd_init(void); diff --git a/include/linkedlists.h b/include/linkedlists.h index 2c38fe5..cbda07e 100644 --- a/include/linkedlists.h +++ b/include/linkedlists.h @@ -453,6 +453,56 @@ struct { \ __elm_outer; \ }) +/*! + * \brief Removes a specific entry from a list, by an attribute string comparison match. + * \param head This is a pointer to the list head structure + * \param attribute The attribute by which to look for a match via string comparison + * \param value The value that the attribute must have to match + * \param field This is the name of the field (declared using RWLIST_ENTRY()) + * used to link entries of this list together. + * \retval Removed element + * \retval NULL if no matching element was found. + * \warning The removed entry is \b not freed. + */ +#define RWLIST_REMOVE_BY_STRING_FIELD(head, attribute, value, field) \ + ({ \ + typeof((head)->first) __elm = NULL; \ + if ((head)->first && !strcmp((head)->first->attribute, value)) { \ + __elm = (head)->first; \ + (head)->first = __elm->field.next; \ + __elm->field.next = NULL; \ + if ((head)->last == __elm) { \ + (head)->last = NULL; \ + } \ + } else { \ + typeof((head)->first) __prev = (head)->first; \ + while (__prev && __prev->field.next && strcmp(__prev->field.next->attribute, value)) { \ + __prev = __prev->field.next; \ + } \ + if (__prev) { \ + __elm = (__prev)->field.next; \ + if (__elm) { \ + __prev->field.next = __elm->field.next; \ + __elm->field.next = NULL; \ + } \ + if ((head)->last == __elm) { \ + (head)->last = __prev; \ + } \ + } else { \ + __elm = NULL; \ + } \ + } \ + __elm; \ + }) + +#define RWLIST_WRLOCK_REMOVE_BY_STRING_FIELD(head, attribute, value, field) ({ \ + typeof((head)->first) __elm_outer = NULL; \ + RWLIST_WRLOCK(head); \ + __elm_outer = RWLIST_REMOVE_BY_STRING_FIELD(head, attribute, value, field); \ + RWLIST_UNLOCK(head); \ + __elm_outer; \ +}) + /*! * \brief Removes all the entries from a list and invokes a destructor on each entry * \param head This is a pointer to the list head structure diff --git a/include/socket.h b/include/socket.h index fe8f19a..93c8509 100644 --- a/include/socket.h +++ b/include/socket.h @@ -23,7 +23,9 @@ * \param gid Group ID. -1 to not change. * \retval 0 on success, -1 on failure */ -int bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_t uid, gid_t gid); +#define bbs_make_unix_socket(sock, sockfile, perm, uid, gid) __bbs_make_unix_socket(sock, sockfile, perm, uid, gid, __FILE__, __LINE__, __func__) + +int __bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_t uid, gid_t gid, const char *file, int line, const char *func); /*! * \brief Create a TCP socket @@ -31,7 +33,9 @@ int bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_ * \param port Port number on which to create the socket * \retval 0 on success, -1 on failure */ -int bbs_make_tcp_socket(int *sock, int port); +#define bbs_make_tcp_socket(sock, port) __bbs_make_tcp_socket(sock, port, __FILE__, __LINE__, __func__) + +int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const char *func); /*! \brief Put a socket in nonblocking mode */ int bbs_unblock_fd(int fd); @@ -63,7 +67,9 @@ int bbs_resolve_hostname(const char *hostname, char *buf, size_t len); * \retval -1 on failure, socket file descriptor otherwise * \note This does not perform TLS negotiation, use ssl_client_new immediately or later in the session for encryption. */ -int bbs_tcp_connect(const char *hostname, int port); +#define bbs_tcp_connect(hostname, port) __bbs_tcp_connect(hostname, port, __FILE__, __LINE__, __func__) + +int __bbs_tcp_connect(const char *hostname, int port, const char *file, int line, const char *func); /*! * \brief Wrapper around accept(), with poll timeout diff --git a/include/string.h b/include/string.h index a7fcebe..cfa5f63 100644 --- a/include/string.h +++ b/include/string.h @@ -158,7 +158,10 @@ int bbs_str_isprint(const char *restrict s) __attribute__ ((pure)); int bbs_str_anyprint(const char *restrict s) __attribute__ ((pure)); /*! \brief Convert a string to all lowercase */ -void str_tolower(char *restrict s); +void bbs_str_tolower(char *restrict s); + +/*! \brief Convert a string to all uppercase */ +void bbs_str_toupper(char *restrict s); /*! * \brief Skip a number of occurences of a character in a string diff --git a/modules/mod_asterisk_ami.c b/modules/mod_asterisk_ami.c index 3e2797c..bbc1287 100644 --- a/modules/mod_asterisk_ami.c +++ b/modules/mod_asterisk_ami.c @@ -84,6 +84,8 @@ static void ami_callback(struct ami_event *event) ami_event_free(event); /* Free event when done with it */ } +static int ami_alert_pipe[2] = { -1, -1 }; + int __bbs_ami_callback_register(int (*callback)(struct ami_event *event, const char *eventname), void *mod) { struct ami_callback *cb; @@ -96,6 +98,8 @@ int __bbs_ami_callback_register(int (*callback)(struct ami_event *event, const c cb->callback = callback; cb->mod = mod; + bbs_alertpipe_write(ami_alert_pipe); + RWLIST_WRLOCK(&callbacks); RWLIST_INSERT_HEAD(&callbacks, cb, entry); RWLIST_UNLOCK(&callbacks); @@ -107,6 +111,11 @@ int bbs_ami_callback_unregister(int (*callback)(struct ami_event *event, const c { struct ami_callback *cb; + /* If ami_disconnect_callback is currently running, + * we need to interrupt it in order to be able to + * successfully WRLOCK the list. */ + bbs_alertpipe_write(ami_alert_pipe); + RWLIST_WRLOCK(&callbacks); cb = RWLIST_REMOVE_BY_FIELD(&callbacks, callback, callback, entry); RWLIST_UNLOCK(&callbacks); @@ -146,8 +155,8 @@ static struct bbs_cli_entry cli_commands_ami[] = { static int load_config(int open_logfile); -static int ami_alert_pipe[2] = { -1, -1 }; static int unloading = 0; +static int reconnecting = 0; static void ami_disconnect_callback(void) { @@ -160,26 +169,38 @@ static void ami_disconnect_callback(void) return; /* If we're unloading, don't care */ } + reconnecting = 1; RWLIST_RDLOCK(&callbacks); /* Perhaps Asterisk restarted (or crashed). * Try to reconnect if it comes back up. */ for (;;) { - int res = load_config(0); + int res; + res = load_config(0); if (!res) { bbs_verb(4, "Asterisk Manager Interface connection re-established\n"); set_ami_status(1); break; } + bbs_debug(3, "Waiting %d ms to retry AMI connection...\n", sleep_ms); if (bbs_alertpipe_poll(ami_alert_pipe, sleep_ms)) { bbs_alertpipe_read(ami_alert_pipe); bbs_debug(3, "AMI reconnect interrupted\n"); - break; + if (unloading) { + break; + } + /* Interrupted, but not unloading. + * Probably something else that needs to grab a list lock. + * Suspend the retry for a moment. */ + RWLIST_UNLOCK(&callbacks); + usleep(1); /* Enough to cause the CPU to suspend this thread, and allow something else to grab a WRLOCK */ + RWLIST_WRLOCK(&callbacks); } if (sleep_ms < 64000) { /* Exponential backoff, up to 64 seconds */ sleep_ms *= 2; } } RWLIST_UNLOCK(&callbacks); + reconnecting = 0; } static int load_config(int open_logfile) @@ -253,8 +274,11 @@ static int unload_module(void) /* If anything was in the body of ami_disconnect_callback, * it had the list locked. If we can lock the list, that means they're gone. */ - RWLIST_WRLOCK(&callbacks); - RWLIST_UNLOCK(&callbacks); + do { + bbs_debug(3, "Attempting to lock callback list to ensure safe unload\n"); + RWLIST_WRLOCK(&callbacks); + RWLIST_UNLOCK(&callbacks); + } while (reconnecting); bbs_cli_unregister_multiple(cli_commands_ami); ami_disconnect(); diff --git a/modules/mod_asterisk_queues.c b/modules/mod_asterisk_queues.c index 532fe12..33b06e8 100644 --- a/modules/mod_asterisk_queues.c +++ b/modules/mod_asterisk_queues.c @@ -62,6 +62,7 @@ struct agent { struct bbs_node *node; /*!< Agent's node */ unsigned int idle:1; /*!< Currently idle? */ unsigned int gotwritten:1; /*!< Another thread wrote onto our terminal while we were idle */ + unsigned int stale:1; /*!< Needs stats update */ RWLIST_ENTRY(agent) entry; }; @@ -94,6 +95,7 @@ struct queue { int calls; /*!< Total # calls */ int completed; /*!< # completed calls */ int abandoned; /*!< # abandoned calls */ + unsigned int stale:1; /*!< Needs stats initialization or update */ RWLIST_ENTRY(queue) entry; char data[]; }; @@ -143,7 +145,7 @@ int bbs_queue_call_handler_unregister(const char *name) struct queue_call_handler *qch; RWLIST_WRLOCK(&handlers); - qch = RWLIST_REMOVE_BY_FIELD(&handlers, name, name, entry); + qch = RWLIST_REMOVE_BY_STRING_FIELD(&handlers, name, name, entry); RWLIST_UNLOCK(&handlers); if (!qch) { @@ -229,16 +231,45 @@ static struct queue *find_queue(const char *name) return NULL; } -static int queues_init(void) +static int update_queue_stats(void) { int i; - /* Initially, get all stats for all queues. */ - struct ami_response *resp = ami_action("QueueStatus", ""); + struct queue *q, *lastq; + int stale_queues = 0; + struct ami_response *resp; + + /* Initially (and as needed), get all stats for all queues. */ + RWLIST_RDLOCK(&queues); + RWLIST_TRAVERSE(&queues, q, entry) { + if (q->stale) { + /* As long as at least one queue needs to have stats refreshed, make an AMI request. + * If none do, we can avoid making a request altogether. + * We need to update periodically to update global queue stats + * (and similarly for each queue agent), since these change whenever calls are processed. + * We could manually update these stats ourselves based on events, + * but this is probably the easiest way to keep in sync, albeit somewhat wasteful. */ + stale_queues++; + lastq = q; + } + } + if (!stale_queues) { + /* No queues are stale right now, skip update. */ + RWLIST_UNLOCK(&queues); + return 0; + } + + /* If only one queue needs to be refreshed, then just ask for that one by name. */ + resp = ami_action("QueueStatus", stale_queues == 1 ? lastq->name : ""); + if (!resp || !resp->success) { + RWLIST_UNLOCK(&queues); + if (resp) { + ami_resp_free(resp); + } bbs_error("Failed to get queue stats\n"); return -1; } - RWLIST_RDLOCK(&queues); + for (i = 1; i < resp->size - 1; i++) { struct ami_event *e = resp->events[i]; const char *event = ami_keyvalue(e, "Event"); @@ -250,6 +281,9 @@ static int queues_init(void) bbs_debug(5, "Skipping irrelevant queue '%s'\n", queue_name); continue; /* Not one of our queues that we care about */ } + if (!queue->stale) { + continue; /* Queue is already up to date, don't care */ + } numcalls = ami_keyvalue(e, "Calls"); completed = ami_keyvalue(e, "Completed"); abandoned = ami_keyvalue(e, "Abandoned"); @@ -261,12 +295,12 @@ static int queues_init(void) queue->calls = atoi(numcalls); queue->completed = atoi(completed); queue->abandoned = atoi(abandoned); - bbs_verb(5, "Added queue %s\n", queue->name); + bbs_debug(1, "Updated stats for queue %s\n", queue->name); + queue->stale = 0; } } RWLIST_UNLOCK(&queues); ami_resp_free(resp); /* Free response when done with it */ - bbs_verb(4, "All queues are initialized\n"); return 0; } @@ -491,6 +525,8 @@ static int ami_callback(struct ami_event *e, const char *eventname) /* Events that print to agents' terminals use \r instead of \n * to overwrite the current timestamp */ + queue->stale = 1; /* The stats are now stale, and will need to be updated at the next convenient opportunity. */ + if (!strcmp(eventname, "QueueCallerJoin")) { char *queueid, *ani2, *dnis; const char *callerid, *channel, *callername; @@ -545,11 +581,24 @@ static int ami_callback(struct ami_event *e, const char *eventname) #endif } else if (!strcmp(eventname, "AgentConnect")) { const char *holdtime, *ringtime, *member_name, *callerid; + struct agent *agent; + int agentid; member_name = ami_keyvalue(e, "MemberName"); holdtime = ami_keyvalue(e, "HoldTime"); ringtime = ami_keyvalue(e, "RingTime"); callerid = ami_keyvalue(e, "CallerIDNum"); agent_printf(queue, member_name, "%s\r%-15s %-20s %15s [%s/%s]\n", COLOR_RESET, "ACD ANS", queue->title, S_OR(callerid, ""), S_OR(holdtime, ""), S_OR(ringtime, "")); + + /* Since this agent has taken a call, this means the agent's statistics + * will need to be updated to reflect having answered this call. */ + agentid = atoi(member_name); + RWLIST_RDLOCK(&agents); + RWLIST_TRAVERSE(&agents, agent, entry) { + if (agent->id == agentid) { + agent->stale = 1; + } + } + RWLIST_UNLOCK(&agents); } else { bbs_debug(6, "Ignoring queue event: %s\n", eventname); /* We know it's queue related since it contains a Queue key */ return -1; @@ -558,8 +607,7 @@ static int ami_callback(struct ami_event *e, const char *eventname) return 0; } -/* XXX CallsTaken is only set when door execution begins, and isn't updated afterwards during execution */ -static int membership_init(struct agent *agent) +static int update_member_stats(struct agent *agent) { int i; struct ami_response *resp = ami_action("QueueStatus", "Member:%d", agent->id); @@ -567,6 +615,9 @@ static int membership_init(struct agent *agent) /* We still need to initialize the agent-specific stats for all queues. * This response will be smaller (maybe much smaller) than asking for everything. */ if (!resp || !resp->success) { + if (resp) { + ami_resp_free(resp); + } bbs_error("Failed to get queue status for agent %d\n", agent->id); return -1; } @@ -628,6 +679,12 @@ static int queues_status(struct agent *agent) { struct queue *queue; + update_queue_stats(); + if (agent->stale) { + update_member_stats(agent); + agent->stale = 0; + } + bbs_node_writef(agent->node, "%-22s %6s\t%6s\t%6s\t%6s\n", "===== ACD QUEUE =====", "!!", "+", "-", "@"); RWLIST_RDLOCK(&queues); RWLIST_TRAVERSE(&queues, queue, entry) { @@ -863,7 +920,7 @@ static int agent_exec(struct bbs_node *node, const char *args) return 0; } - if (membership_init(agent)) { + if (update_member_stats(agent)) { goto cleanup; } @@ -1006,6 +1063,7 @@ static int load_config(void) SET_FSM_STRING_VAR(queue, data, name, name, namelen); SET_FSM_STRING_VAR(queue, data, title, title, titlelen); SET_FSM_STRING_VAR(queue, data, handler, handler, handlerlen); + queue->stale = 1; /* Needs to be initialized with queue stats */ RWLIST_INSERT_TAIL(&queues, queue, entry); bbs_debug(4, "Added queue '%s'\n", name); } @@ -1036,7 +1094,7 @@ static int load_module(void) RWLIST_REMOVE_ALL(&queues, entry, free); return -1; } - if (queues_init()) { + if (update_queue_stats()) { RWLIST_REMOVE_ALL(&queues, entry, free); RWLIST_REMOVE_ALL(&calls, entry, free); return -1; diff --git a/modules/mod_mail_trash.c b/modules/mod_mail_trash.c index bd9c858..7e426b3 100644 --- a/modules/mod_mail_trash.c +++ b/modules/mod_mail_trash.c @@ -126,8 +126,8 @@ static void *trash_monitor(void *unused) bbs_pthread_disable_cancel(); scan_mailboxes(); bbs_pthread_enable_cancel(); - /* Not necessary to run more frequently than once per hour. */ - sleep(60 * 60); /* use sleep instead of usleep since the argument to usleep would overflow an int */ + /* Not necessary to run more frequently than twice per day. */ + sleep(60 * 60 * 12); /* use sleep instead of usleep since the argument to usleep would overflow an int */ } return NULL; } diff --git a/nets/net_imap/imap_server_list.c b/nets/net_imap/imap_server_list.c index e9fda04..09e1053 100644 --- a/nets/net_imap/imap_server_list.c +++ b/nets/net_imap/imap_server_list.c @@ -397,7 +397,7 @@ int list_iterate(struct imap_session *imap, struct list_command *lcmd, int level bbs_warning("No user for maildir %s\n", entry->d_name); goto cleanup; } - str_tolower(mailboxbuf); /* Convert to all lowercase since that's the convention we use for email */ + bbs_str_tolower(mailboxbuf); /* Convert to all lowercase since that's the convention we use for email */ mailboxname = mailboxbuf; /* e.g. jsmith instead of 1 */ } else if (level == 0 && lcmd->ns == NAMESPACE_SHARED && !isdigit(*entry->d_name)) { mailboxname = entry->d_name; /* Mailbox name stays the same, this is technically a redundant instruction */ @@ -482,7 +482,7 @@ int list_scandir(struct imap_session *imap, struct list_command *lcmd, int level bbs_warning("No user for maildir %s\n", entry->d_name); goto cleanup; } - str_tolower(mailboxbuf); /* Convert to all lowercase since that's the convention we use for email */ + bbs_str_tolower(mailboxbuf); /* Convert to all lowercase since that's the convention we use for email */ mailboxname = mailboxbuf; /* e.g. jsmith instead of 1 */ } else if (level == 0 && lcmd->ns == NAMESPACE_SHARED && !isdigit(*entry->d_name)) { mailboxname = entry->d_name; /* Mailbox name stays the same, this is technically a redundant instruction */