Merge "Revert "AGI: Only defer frames when in an interception routine.""
[asterisk/asterisk.git] / cdr / cdr_tds.c
index 2046f89..f3d0628 100644 (file)
@@ -14,8 +14,8 @@
  * at the top of the source tree.
  */
 
-/*! \file
- *
+/*!
+ * \file
  * \brief FreeTDS CDR logger
  *
  * See also
@@ -24,7 +24,8 @@
  * \ingroup cdr_drivers
  */
 
-/*! \verbatim
+/*!
+ * \verbatim
  *
  * Table Structure for `cdr`
  *
@@ -48,7 +49,8 @@ CREATE TABLE [dbo].[cdr] (
        [billsec] [int] NULL ,
        [disposition] [varchar] (20) NULL ,
        [amaflags] [varchar] (16) NULL ,
-       [uniqueid] [varchar] (32) NULL
+       [uniqueid] [varchar] (32) NULL ,
+       [userfield] [varchar] (256) NULL
 ) ON [PRIMARY]
 
 \endverbatim
@@ -57,15 +59,11 @@ CREATE TABLE [dbo].[cdr] (
 
 /*** MODULEINFO
        <depend>freetds</depend>
+       <support_level>extended</support_level>
  ***/
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <time.h>
-#include <math.h>
-
 #include "asterisk/config.h"
 #include "asterisk/channel.h"
 #include "asterisk/cdr.h"
@@ -76,8 +74,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #define DATE_FORMAT "%Y/%m/%d %T"
 
-static char *name = "FreeTDS (MSSQL)";
-static char *config = "cdr_tds.conf";
+static const char name[] = "FreeTDS (MSSQL)";
+static const char config[] = "cdr_tds.conf";
 
 struct cdr_tds_config {
        AST_DECLARE_STRING_FIELDS(
@@ -88,9 +86,11 @@ struct cdr_tds_config {
                AST_STRING_FIELD(table);
                AST_STRING_FIELD(charset);
                AST_STRING_FIELD(language);
+               AST_STRING_FIELD(hrtime);
        );
        DBPROCESS *dbproc;
        unsigned int connected:1;
+       unsigned int has_userfield:1;
 };
 
 AST_MUTEX_DEFINE_STATIC(tds_lock);
@@ -100,15 +100,19 @@ static struct cdr_tds_config *settings;
 static char *anti_injection(const char *, int);
 static void get_date(char *, size_t len, struct timeval);
 
+static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
+       __attribute__((format(printf, 2, 3)));
+
 static int mssql_connect(void);
 static int mssql_disconnect(void);
 
 static int tds_log(struct ast_cdr *cdr)
 {
        char start[80], answer[80], end[80];
-       char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid;
+       char *accountcode, *src, *dst, *dcontext, *clid, *channel, *dstchannel, *lastapp, *lastdata, *uniqueid, *userfield = NULL;
        RETCODE erc;
        int res = -1;
+       int attempt = 1;
 
        accountcode = anti_injection(cdr->accountcode, 20);
        src         = anti_injection(cdr->src, 80);
@@ -127,83 +131,143 @@ static int tds_log(struct ast_cdr *cdr)
 
        ast_mutex_lock(&tds_lock);
 
+       if (settings->has_userfield) {
+               userfield = anti_injection(cdr->userfield, AST_MAX_USER_FIELD);
+       }
+
+retry:
        /* Ensure that we are connected */
        if (!settings->connected) {
+               ast_log(LOG_NOTICE, "Attempting to reconnect to %s (Attempt %d)\n", settings->hostname, attempt);
                if (mssql_connect()) {
                        /* Connect failed */
+                       if (attempt++ < 3) {
+                               goto retry;
+                       }
                        goto done;
                }
        }
 
-       erc = dbfcmd(settings->dbproc,
-               "INSERT INTO %s "
-               "("
-                       "accountcode, "
-                       "src, "
-                       "dst, "
-                       "dcontext, "
-                       "clid, "
-                       "channel, "
-                       "dstchannel, "
-                       "lastapp, "
-                       "lastdata, "
-                       "start, "
-                       "answer, "
-                       "[end], "
-                       "duration, "
-                       "billsec, "
-                       "disposition, "
-                       "amaflags, "
-                       "uniqueid"
-               ") "
-               "VALUES "
-               "("
-                       "'%s', "        /* accountcode */
-                       "'%s', "        /* src */
-                       "'%s', "        /* dst */
-                       "'%s', "        /* dcontext */
-                       "'%s', "        /* clid */
-                       "'%s', "        /* channel */
-                       "'%s', "        /* dstchannel */
-                       "'%s', "        /* lastapp */
-                       "'%s', "        /* lastdata */
-                       "%s, "          /* start */
-                       "%s, "          /* answer */
-                       "%s, "          /* end */
-                       "%ld, "         /* duration */
-                       "%ld, "         /* billsec */
-                       "'%s', "        /* disposition */
-                       "'%s', "        /* amaflags */
-                       "'%s'"          /* uniqueid */
-               ")",
-               settings->table,
-               accountcode,
-               src,
-               dst,
-               dcontext,
-               clid,
-               channel,
-               dstchannel,
-               lastapp,
-               lastdata,
-               start,
-               answer,
-               end,
-               cdr->duration,
-               cdr->billsec,
-               ast_cdr_disp2str(cdr->disposition),
-               ast_cdr_flags2str(cdr->amaflags),
-               uniqueid
-       );
+       if (settings->has_userfield) {
+               if (settings->hrtime) {
+                       double hrbillsec = 0.0;
+                       double hrduration;
+
+                       if (!ast_tvzero(cdr->answer)) {
+                               hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
+                       }
+                       hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
+
+                       erc = dbfcmd(settings->dbproc,
+                                        "INSERT INTO %s "
+                                        "("
+                                        "accountcode, src, dst, dcontext, clid, channel, "
+                                        "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
+                                        "billsec, disposition, amaflags, uniqueid, userfield"
+                                        ") "
+                                        "VALUES "
+                                        "("
+                                        "'%s', '%s', '%s', '%s', '%s', '%s', "
+                                        "'%s', '%s', '%s', %s, %s, %s, %lf, "
+                                        "%lf, '%s', '%s', '%s', '%s'"
+                                        ")",
+                                        settings->table,
+                                        accountcode, src, dst, dcontext, clid, channel,
+                                        dstchannel, lastapp, lastdata, start, answer, end, hrduration,
+                                        hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
+                                        userfield
+                       );
+               } else {
+                       erc = dbfcmd(settings->dbproc,
+                                        "INSERT INTO %s "
+                                        "("
+                                        "accountcode, src, dst, dcontext, clid, channel, "
+                                        "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
+                                        "billsec, disposition, amaflags, uniqueid, userfield"
+                                        ") "
+                                        "VALUES "
+                                        "("
+                                        "'%s', '%s', '%s', '%s', '%s', '%s', "
+                                        "'%s', '%s', '%s', %s, %s, %s, %ld, "
+                                        "%ld, '%s', '%s', '%s', '%s'"
+                                        ")",
+                                        settings->table,
+                                        accountcode, src, dst, dcontext, clid, channel,
+                                        dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
+                                        cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
+                                        userfield
+                       );
+               }
+       } else {
+               if (settings->hrtime) {
+                       double hrbillsec = 0.0;
+                       double hrduration;
+
+                       if (!ast_tvzero(cdr->answer)) {
+                               hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
+                       }
+                       hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
+
+                       erc = dbfcmd(settings->dbproc,
+                                        "INSERT INTO %s "
+                                        "("
+                                        "accountcode, src, dst, dcontext, clid, channel, "
+                                        "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
+                                        "billsec, disposition, amaflags, uniqueid"
+                                        ") "
+                                        "VALUES "
+                                        "("
+                                        "'%s', '%s', '%s', '%s', '%s', '%s', "
+                                        "'%s', '%s', '%s', %s, %s, %s, %lf, "
+                                        "%lf, '%s', '%s', '%s'"
+                                        ")",
+                                        settings->table,
+                                        accountcode, src, dst, dcontext, clid, channel,
+                                        dstchannel, lastapp, lastdata, start, answer, end, hrduration,
+                                        hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
+                       );
+               } else {
+                       erc = dbfcmd(settings->dbproc,
+                                        "INSERT INTO %s "
+                                        "("
+                                        "accountcode, src, dst, dcontext, clid, channel, "
+                                        "dstchannel, lastapp, lastdata, start, answer, [end], duration, "
+                                        "billsec, disposition, amaflags, uniqueid"
+                                        ") "
+                                        "VALUES "
+                                        "("
+                                        "'%s', '%s', '%s', '%s', '%s', '%s', "
+                                        "'%s', '%s', '%s', %s, %s, %s, %ld, "
+                                        "%ld, '%s', '%s', '%s'"
+                                        ")",
+                                        settings->table,
+                                        accountcode, src, dst, dcontext, clid, channel,
+                                        dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
+                                        cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
+                       );
+               }
+       }
 
        if (erc == FAIL) {
-               ast_log(LOG_ERROR, "Failed to build INSERT statement, no CDR was logged.\n");
-               goto done;
+               if (attempt++ < 3) {
+                       ast_log(LOG_NOTICE, "Failed to build INSERT statement, retrying...\n");
+                       mssql_disconnect();
+                       goto retry;
+               } else {
+                       ast_log(LOG_ERROR, "Failed to build INSERT statement, no CDR was logged.\n");
+                       goto done;
+               }
        }
 
        if (dbsqlexec(settings->dbproc) == FAIL) {
-               ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n");
-               goto done;
+               if (attempt++ < 3) {
+                       ast_log(LOG_NOTICE, "Failed to execute INSERT statement, retrying...\n");
+                       mssql_disconnect();
+                       goto retry;
+               } else {
+                       ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CDR was logged.\n");
+                       goto done;
+               }
        }
 
        /* Consume any results we might get back (this is more of a sanity check than
@@ -228,6 +292,10 @@ done:
        ast_free(lastdata);
        ast_free(uniqueid);
 
+       if (userfield) {
+               ast_free(userfield);
+       }
+
        return res;
 }
 
@@ -277,6 +345,37 @@ static void get_date(char *dateField, size_t len, struct timeval when)
        }
 }
 
+static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
+{
+       va_list ap;
+       char *buffer;
+
+       va_start(ap, fmt);
+       if (ast_vasprintf(&buffer, fmt, ap) < 0) {
+               va_end(ap);
+               return 1;
+       }
+       va_end(ap);
+
+       if (dbfcmd(dbproc, buffer) == FAIL) {
+               ast_free(buffer);
+               return 1;
+       }
+
+       ast_free(buffer);
+
+       if (dbsqlexec(dbproc) == FAIL) {
+               return 1;
+       }
+
+       /* Consume the result set (we don't really care about the result, though) */
+       while (dbresults(dbproc) != NO_MORE_RESULTS) {
+               while (dbnextrow(dbproc) != NO_MORE_ROWS);
+       }
+
+       return 0;
+}
+
 static int mssql_disconnect(void)
 {
        if (settings->dbproc) {
@@ -317,19 +416,17 @@ static int mssql_connect(void)
                goto failed;
        }
 
-       if (dbfcmd(settings->dbproc, "SELECT 1 FROM [%s]", settings->table) == FAIL) {
-               ast_log(LOG_ERROR, "Unable to build query while verifying the existence of table '%s'\n", settings->table);
+       if (execute_and_consume(settings->dbproc, "SELECT 1 FROM [%s] WHERE 1 = 0", settings->table)) {
+               ast_log(LOG_ERROR, "Unable to find table '%s'\n", settings->table);
                goto failed;
        }
 
-       if (dbsqlexec(settings->dbproc) == FAIL) {
-               ast_log(LOG_ERROR, "Unable to verify existence of table '%s'\n", settings->table);
-               goto failed;
-       }
-
-       /* Consume the result set (we don't really care about the result, though) */
-       while (dbresults(settings->dbproc) != NO_MORE_RESULTS) {
-               while (dbnextrow(settings->dbproc) != NO_MORE_ROWS);
+       /* Check to see if we have a userfield column in the table */
+       if (execute_and_consume(settings->dbproc, "SELECT userfield FROM [%s] WHERE 1 = 0", settings->table)) {
+               ast_log(LOG_NOTICE, "Unable to find 'userfield' column in table '%s'\n", settings->table);
+               settings->has_userfield = 0;
+       } else {
+               settings->has_userfield = 1;
        }
 
        settings->connected = 1;
@@ -344,6 +441,10 @@ failed:
 
 static int tds_unload_module(void)
 {
+       if (ast_cdr_unregister(name)) {
+               return -1;
+       }
+
        if (settings) {
                ast_mutex_lock(&tds_lock);
                mssql_disconnect();
@@ -353,8 +454,6 @@ static int tds_unload_module(void)
                ast_free(settings);
        }
 
-       ast_cdr_unregister(name);
-
        dbexit();
 
        return 0;
@@ -386,7 +485,7 @@ static int tds_load_module(int reload)
        struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 
        cfg = ast_config_load(config, config_flags);
-       if (!cfg) {
+       if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
                ast_log(LOG_NOTICE, "Unable to load TDS config for CDRs: %s\n", config);
                return 0;
        } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
@@ -403,12 +502,19 @@ static int tds_load_module(int reload)
        /* Clear out any existing settings */
        ast_string_field_init(settings, 0);
 
-       ptr = ast_variable_retrieve(cfg, "global", "hostname");
+       /* 'connection' is the new preferred configuration option */
+       ptr = ast_variable_retrieve(cfg, "global", "connection");
        if (ptr) {
                ast_string_field_set(settings, hostname, ptr);
        } else {
-               ast_log(LOG_ERROR, "Failed to connect: Database server hostname not specified.\n");
-               goto failed;
+               /* But we keep 'hostname' for backwards compatibility */
+               ptr = ast_variable_retrieve(cfg, "global", "hostname");
+               if (ptr) {
+                       ast_string_field_set(settings, hostname, ptr);
+               } else {
+                       ast_log(LOG_ERROR, "Failed to connect: Database server connection not specified.\n");
+                       goto failed;
+               }
        }
 
        ptr = ast_variable_retrieve(cfg, "global", "dbname");
@@ -457,6 +563,13 @@ static int tds_load_module(int reload)
                ast_string_field_set(settings, table, "cdr");
        }
 
+       ptr = ast_variable_retrieve(cfg, "global", "hrtime");
+       if (ptr && ast_true(ptr)) {
+               ast_string_field_set(settings, hrtime, ptr);
+       } else {
+               ast_log(LOG_NOTICE, "High Resolution Time not found, using integers for billsec and duration fields by default.\n");
+       }
+
        mssql_disconnect();
 
        if (mssql_connect()) {
@@ -491,13 +604,9 @@ static int load_module(void)
        dberrhandle(tds_error_handler);
        dbmsghandle(tds_message_handler);
 
-       settings = ast_calloc(1, sizeof(*settings));
+       settings = ast_calloc_with_stringfields(1, struct cdr_tds_config, 256);
 
-       if (!settings || ast_string_field_init(settings, 256)) {
-               if (settings) {
-                       ast_free(settings);
-                       settings = NULL;
-               }
+       if (!settings) {
                dbexit();
                return AST_MODULE_LOAD_DECLINE;
        }
@@ -520,8 +629,10 @@ static int unload_module(void)
        return tds_unload_module();
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "FreeTDS CDR Backend",
-               .load = load_module,
-               .unload = unload_module,
-               .reload = reload,
-              );
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "FreeTDS CDR Backend",
+       .support_level = AST_MODULE_SUPPORT_EXTENDED,
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload,
+       .load_pri = AST_MODPRI_CDR_DRIVER,
+);