CDR: Fix deadlock setting some CDR values.
[asterisk/asterisk.git] / funcs / func_cdr.c
index 44ecf4d..2dd9f15 100644 (file)
@@ -31,8 +31,6 @@
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
 #include "asterisk/pbx.h"
@@ -180,7 +178,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                <para>Write-Only</para>
                                        </enum>
                                        <enum name="disable">
-                                               <para>Disable CDRs for this channel.</para>
+                                               <para>Setting to 1 will disable CDRs for this channel.
+                                               Setting to 0 will enable CDRs for this channel.</para>
                                                <para>Write-Only</para>
                                        </enum>
                                </enumlist>
@@ -220,6 +219,32 @@ STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_read_message_type);
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_write_message_type);
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_prop_write_message_type);
 
+static struct timeval cdr_retrieve_time(struct ast_channel *chan, const char *time_name)
+{
+       struct timeval time = { 0 };
+       char *value = NULL;
+       char tempbuf[128];
+       long int tv_sec;
+       long int tv_usec;
+
+       if (ast_strlen_zero(ast_channel_name(chan))) {
+               /* Format request on a dummy channel */
+               ast_cdr_format_var(ast_channel_cdr(chan), time_name, &value, tempbuf, sizeof(tempbuf), 1);
+       } else {
+               ast_cdr_getvar(ast_channel_name(chan), time_name, tempbuf, sizeof(tempbuf));
+       }
+
+       /* time.tv_usec is suseconds_t, which could be int or long */
+       if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) == 2) {
+               time.tv_sec = tv_sec;
+               time.tv_usec = tv_usec;
+       } else {
+               ast_log(AST_LOG_WARNING, "Failed to fully extract '%s' from CDR\n", time_name);
+       }
+
+       return time;
+}
+
 static void cdr_read_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 {
        struct cdr_func_payload *payload = stasis_message_data(message);
@@ -255,7 +280,7 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
 
        if (ast_strlen_zero(ast_channel_name(payload->chan))) {
                /* Format request on a dummy channel */
-               ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), 0);
+               ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), ast_test_flag(&flags, OPT_UNPARSED));
                if (ast_strlen_zero(value)) {
                        return;
                }
@@ -267,16 +292,21 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
 
        if (ast_test_flag(&flags, OPT_FLOAT)
                && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
-               long ms;
-               double dtime;
+               struct timeval start = cdr_retrieve_time(payload->chan, !strcasecmp("billsec", args.variable) ? "answer" : "start");
+               struct timeval finish = cdr_retrieve_time(payload->chan, "end");
+               double delta;
 
-               if (sscanf(tempbuf, "%30ld", &ms) != 1) {
-                       ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
-                               args.variable, tempbuf, ast_channel_name(payload->chan));
-                       return;
+               if (ast_tvzero(finish)) {
+                       finish = ast_tvnow();
+               }
+
+               if (ast_tvzero(start)) {
+                       delta = 0.0;
+               } else {
+                       delta = (double)(ast_tvdiff_us(finish, start) / 1000000.0);
                }
-               dtime = (double)(ms / 1000.0);
-               snprintf(tempbuf, sizeof(tempbuf), "%lf", dtime);
+               snprintf(tempbuf, sizeof(tempbuf), "%lf", delta);
+
        } else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
                if (!strcasecmp("start", args.variable)
                        || !strcasecmp("end", args.variable)
@@ -284,14 +314,16 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
                        struct timeval fmt_time;
                        struct ast_tm tm;
                        /* tv_usec is suseconds_t, which could be int or long */
+                       long int tv_sec;
                        long int tv_usec;
 
-                       if (sscanf(tempbuf, "%ld.%ld", &fmt_time.tv_sec, &tv_usec) != 2) {
+                       if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) != 2) {
                                ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
                                        args.variable, tempbuf, ast_channel_name(payload->chan));
                                return;
                        }
-                       if (fmt_time.tv_sec) {
+                       if (tv_sec) {
+                               fmt_time.tv_sec = tv_sec;
                                fmt_time.tv_usec = tv_usec;
                                ast_localtime(&fmt_time, &tm, NULL);
                                ast_strftime(tempbuf, sizeof(tempbuf), "%Y-%m-%d %T", &tm);
@@ -324,7 +356,7 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
 
 static void cdr_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 {
-       struct cdr_func_payload *payload = stasis_message_data(message);
+       struct cdr_func_payload *payload;
        struct ast_flags flags = { 0 };
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(variable);
@@ -335,21 +367,17 @@ static void cdr_write_callback(void *data, struct stasis_subscription *sub, stru
        if (cdr_write_message_type() != stasis_message_type(message)) {
                return;
        }
-
+       payload = stasis_message_data(message);
        if (!payload) {
                return;
        }
-
-       if (ast_strlen_zero(payload->arguments)) {
-               ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
-                       payload->cmd, payload->cmd);
-               return;
-       }
-       if (ast_strlen_zero(payload->value)) {
-               ast_log(AST_LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
-                       payload->cmd, payload->cmd);
+       if (ast_strlen_zero(payload->arguments)
+               || !payload->value) {
+               /* Sanity check.  cdr_write() could never send these bad messages */
+               ast_assert(0);
                return;
        }
+
        parse = ast_strdupa(payload->arguments);
        AST_STANDARD_APP_ARGS(args, parse);
 
@@ -357,32 +385,16 @@ static void cdr_write_callback(void *data, struct stasis_subscription *sub, stru
                ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
        }
 
-       if (!strcasecmp(args.variable, "accountcode")) {
-               ast_log(AST_LOG_WARNING, "Using the CDR function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n");
-               ast_channel_lock(payload->chan);
-               ast_channel_accountcode_set(payload->chan, payload->value);
-               ast_channel_unlock(payload->chan);
-       } else if (!strcasecmp(args.variable, "peeraccount")) {
-               ast_log(AST_LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
-       } else if (!strcasecmp(args.variable, "userfield")) {
+       /* These are already handled by cdr_write() */
+       ast_assert(strcasecmp(args.variable, "accountcode")
+               && strcasecmp(args.variable, "peeraccount")
+               && strcasecmp(args.variable, "amaflags"));
+
+       if (!strcasecmp(args.variable, "userfield")) {
                ast_cdr_setuserfield(ast_channel_name(payload->chan), payload->value);
-       } else if (!strcasecmp(args.variable, "amaflags")) {
-               ast_log(AST_LOG_WARNING, "Using the CDR function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n");
-               if (isdigit(*payload->value)) {
-                       int amaflags;
-                       sscanf(payload->value, "%30d", &amaflags);
-                       ast_channel_lock(payload->chan);
-                       ast_channel_amaflags_set(payload->chan, amaflags);
-                       ast_channel_unlock(payload->chan);
-               } else {
-                       ast_channel_lock(payload->chan);
-                       ast_channel_amaflags_set(payload->chan, ast_channel_string2amaflag(payload->value));
-                       ast_channel_unlock(payload->chan);
-               }
        } else {
                ast_cdr_setvar(ast_channel_name(payload->chan), args.variable, payload->value);
        }
-       return;
 }
 
 static void cdr_prop_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
@@ -437,8 +449,7 @@ static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
                    char *buf, size_t len)
 {
        RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-       RAII_VAR(struct cdr_func_payload *, payload,
-               ao2_alloc(sizeof(*payload), NULL), ao2_cleanup);
+       RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
        struct cdr_func_data output = { 0, };
 
        if (!chan) {
@@ -446,6 +457,13 @@ static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
                return -1;
        }
 
+       if (!cdr_read_message_type()) {
+               ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+
+       payload = ao2_alloc(sizeof(*payload), NULL);
        if (!payload) {
                return -1;
        }
@@ -485,41 +503,100 @@ static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
        return 0;
 }
 
-static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
-                    const char *value)
+static int cdr_write(struct ast_channel *chan, const char *cmd, char *arguments,
+       const char *value)
 {
-       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-       RAII_VAR(struct cdr_func_payload *, payload,
-                    ao2_alloc(sizeof(*payload), NULL), ao2_cleanup);
-       RAII_VAR(struct stasis_message_router *, router,
-                    ast_cdr_message_router(), ao2_cleanup);
+       struct stasis_message *message;
+       struct cdr_func_payload *payload;
+       struct stasis_message_router *router;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(variable);
+               AST_APP_ARG(options);
+       );
+       char *parse;
 
        if (!chan) {
                ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
                return -1;
        }
+       if (ast_strlen_zero(arguments)) {
+               ast_log(LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
+                       cmd, cmd);
+               return -1;
+       }
+       if (!value) {
+               ast_log(LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
+                       cmd, cmd);
+               return -1;
+       }
 
-       if (!router) {
-               ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
+       parse = ast_strdupa(arguments);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       /* These CDR variables are no longer supported or set directly on the channel */
+       if (!strcasecmp(args.variable, "accountcode")) {
+               ast_log(LOG_WARNING, "Using the %s function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n",
+                       cmd);
+               ast_channel_lock(chan);
+               ast_channel_accountcode_set(chan, value);
+               ast_channel_unlock(chan);
+               return 0;
+       }
+       if (!strcasecmp(args.variable, "amaflags")) {
+               int amaflags;
+
+               ast_log(LOG_WARNING, "Using the %s function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n",
+                       cmd);
+               if (isdigit(*value)) {
+                       if (sscanf(value, "%30d", &amaflags) != 1) {
+                               amaflags = AST_AMA_NONE;
+                       }
+               } else {
+                       amaflags = ast_channel_string2amaflag(value);
+               }
+               ast_channel_lock(chan);
+               ast_channel_amaflags_set(chan, amaflags);
+               ast_channel_unlock(chan);
+               return 0;
+       }
+       if (!strcasecmp(args.variable, "peeraccount")) {
+               ast_log(LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
+               return 0;
+       }
+
+       /* The remaining CDR variables are handled by CDR processing code */
+       if (!cdr_write_message_type()) {
+               ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
                        ast_channel_name(chan));
                return -1;
        }
 
+       payload = ao2_alloc(sizeof(*payload), NULL);
        if (!payload) {
                return -1;
        }
        payload->chan = chan;
        payload->cmd = cmd;
-       payload->arguments = parse;
+       payload->arguments = arguments;
        payload->value = value;
 
        message = stasis_message_create(cdr_write_message_type(), payload);
+       ao2_ref(payload, -1);
        if (!message) {
-               ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
+               ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+       router = ast_cdr_message_router();
+       if (!router) {
+               ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
                        ast_channel_name(chan));
+               ao2_ref(message, -1);
                return -1;
        }
        stasis_message_router_publish_sync(router, message);
+       ao2_ref(router, -1);
+       ao2_ref(message, -1);
 
        return 0;
 }
@@ -528,8 +605,7 @@ static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse
                     const char *value)
 {
        RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-       RAII_VAR(struct cdr_func_payload *, payload,
-               ao2_alloc(sizeof(*payload), NULL), ao2_cleanup);
+       RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
        RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
 
        if (!chan) {
@@ -543,6 +619,13 @@ static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse
                return -1;
        }
 
+       if (!cdr_prop_write_message_type()) {
+               ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+
+       payload = ao2_alloc(sizeof(*payload), NULL);
        if (!payload) {
                return -1;
        }
@@ -599,7 +682,7 @@ static int load_module(void)
        int res = 0;
 
        if (!router) {
-               return AST_MODULE_LOAD_FAILURE;
+               return AST_MODULE_LOAD_DECLINE;
        }
 
        res |= STASIS_MESSAGE_TYPE_INIT(cdr_read_message_type);
@@ -615,7 +698,8 @@ static int load_module(void)
                                         cdr_read_callback, NULL);
 
        if (res) {
-               return AST_MODULE_LOAD_FAILURE;
+               unload_module();
+               return AST_MODULE_LOAD_DECLINE;
        }
        return AST_MODULE_LOAD_SUCCESS;
 }