Asterisk - The Open Source Telephony Project  GIT-master-a24979a
Data Structures | Macros | Functions | Variables
cdr_pgsql.c File Reference

PostgreSQL CDR logger. More...

#include "asterisk.h"
#include <libpq-fe.h>
#include "asterisk/config.h"
#include "asterisk/channel.h"
#include "asterisk/cdr.h"
#include "asterisk/cli.h"
#include "asterisk/module.h"
Include dependency graph for cdr_pgsql.c:

Go to the source code of this file.

Data Structures

struct  columns
 
struct  psql_columns
 

Macros

#define DATE_FORMAT   "'%Y-%m-%d %T'"
 
#define LENGTHEN_BUF(size, var_sql)
 
#define LENGTHEN_BUF1(size)    LENGTHEN_BUF(size, sql);
 
#define LENGTHEN_BUF2(size)    LENGTHEN_BUF(size, sql2);
 
#define PGSQL_MIN_VERSION_SCHEMA   70300
 

Functions

static void __reg_module (void)
 
static void __unreg_module (void)
 
struct ast_moduleAST_MODULE_SELF_SYM (void)
 
static int config_module (int reload)
 
static void empty_columns (void)
 
static char * handle_cdr_pgsql_status (struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 Handle the CLI command cdr show pgsql status. More...
 
static int load_module (void)
 
static int pgsql_log (struct ast_cdr *cdr)
 
static void pgsql_reconnect (void)
 
static int reload (void)
 
static int unload_module (void)
 

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "PostgreSQL CDR Backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, .requires = "cdr", }
 
static const struct ast_module_infoast_module_info = &__mod_info
 
static struct ast_cli_entry cdr_pgsql_status_cli []
 
static const char config [] = "cdr_pgsql.conf"
 
static PGconn * conn = NULL
 
static time_t connect_time = 0
 
static int connected = 0
 
static char * encoding
 
static int maxsize = 512
 
static int maxsize2 = 512
 
static const char name [] = "pgsql"
 
static char * pgappname
 
static char * pgdbname
 
static char * pgdbport
 
static char * pgdbuser
 
static char * pghostname
 
static char * pgpassword
 
static ast_mutex_t pgsql_lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, {1, 0} }
 
static struct psql_columns psql_columns = { .first = NULL, .last = NULL, .lock = { PTHREAD_RWLOCK_INITIALIZER , NULL, {1, 0} } , }
 
static int records
 
static char * table
 
static int totalrecords = 0
 
static char * tz
 

Detailed Description

PostgreSQL CDR logger.

Author
Matthew D. Hardeman mhard.nosp@m.emn@.nosp@m.paper.nosp@m.soft.nosp@m..com PostgreSQL http://www.postgresql.org/

Definition in file cdr_pgsql.c.

Macro Definition Documentation

◆ DATE_FORMAT

#define DATE_FORMAT   "'%Y-%m-%d %T'"

Definition at line 56 of file cdr_pgsql.c.

◆ LENGTHEN_BUF

#define LENGTHEN_BUF (   size,
  var_sql 
)

Definition at line 100 of file cdr_pgsql.c.

◆ LENGTHEN_BUF1

#define LENGTHEN_BUF1 (   size)     LENGTHEN_BUF(size, sql);

Definition at line 115 of file cdr_pgsql.c.

◆ LENGTHEN_BUF2

#define LENGTHEN_BUF2 (   size)     LENGTHEN_BUF(size, sql2);

Definition at line 117 of file cdr_pgsql.c.

◆ PGSQL_MIN_VERSION_SCHEMA

#define PGSQL_MIN_VERSION_SCHEMA   70300

Definition at line 58 of file cdr_pgsql.c.

Function Documentation

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 806 of file cdr_pgsql.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 806 of file cdr_pgsql.c.

◆ AST_MODULE_SELF_SYM()

struct ast_module* AST_MODULE_SELF_SYM ( void  )

Definition at line 806 of file cdr_pgsql.c.

◆ config_module()

static int config_module ( int  reload)
static

Definition at line 520 of file cdr_pgsql.c.

521 {
522  char *pgerror;
523  struct columns *cur;
524  PGresult *result;
525  const char *tmp;
526  struct ast_config *cfg;
527  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
528 
529  if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
530  ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
531  return -1;
532  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
533  return 0;
534  }
535 
537 
538  if (!ast_variable_browse(cfg, "global")) {
539  ast_config_destroy(cfg);
541  ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
542  reload ? "reload" : "load");
543  return -1;
544  }
545 
546  if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
547  ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
548  tmp = ""; /* connect via UNIX-socket by default */
549  }
550 
552  if (!(pghostname = ast_strdup(tmp))) {
553  ast_config_destroy(cfg);
555  return -1;
556  }
557 
558  if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
559  ast_log(LOG_WARNING, "PostgreSQL database not specified. Assuming asterisk\n");
560  tmp = "asteriskcdrdb";
561  }
562 
564  if (!(pgdbname = ast_strdup(tmp))) {
565  ast_config_destroy(cfg);
567  return -1;
568  }
569 
570  if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
571  ast_log(LOG_WARNING, "PostgreSQL database user not specified. Assuming asterisk\n");
572  tmp = "asterisk";
573  }
574 
576  if (!(pgdbuser = ast_strdup(tmp))) {
577  ast_config_destroy(cfg);
579  return -1;
580  }
581 
582  if (!(tmp = ast_variable_retrieve(cfg, "global", "appname"))) {
583  tmp = "";
584  }
585 
587  if (!(pgappname = ast_strdup(tmp))) {
588  ast_config_destroy(cfg);
590  return -1;
591  }
592 
593 
594  if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
595  ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
596  tmp = "";
597  }
598 
600  if (!(pgpassword = ast_strdup(tmp))) {
601  ast_config_destroy(cfg);
603  return -1;
604  }
605 
606  if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
607  ast_log(LOG_WARNING, "PostgreSQL database port not specified. Using default 5432.\n");
608  tmp = "5432";
609  }
610 
612  if (!(pgdbport = ast_strdup(tmp))) {
613  ast_config_destroy(cfg);
615  return -1;
616  }
617 
618  if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
619  ast_log(LOG_WARNING, "CDR table not specified. Assuming cdr\n");
620  tmp = "cdr";
621  }
622 
623  ast_free(table);
624  if (!(table = ast_strdup(tmp))) {
625  ast_config_destroy(cfg);
627  return -1;
628  }
629 
630  if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
631  ast_log(LOG_WARNING, "Encoding not specified. Assuming LATIN9\n");
632  tmp = "LATIN9";
633  }
634 
636  if (!(encoding = ast_strdup(tmp))) {
637  ast_config_destroy(cfg);
639  return -1;
640  }
641 
642  if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
643  tmp = "";
644  }
645 
646  ast_free(tz);
647  tz = NULL;
648 
649  if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
650  ast_config_destroy(cfg);
652  return -1;
653  }
654 
655  if (DEBUG_ATLEAST(1)) {
657  ast_log(LOG_DEBUG, "using default unix socket\n");
658  } else {
659  ast_log(LOG_DEBUG, "got hostname of %s\n", pghostname);
660  }
661  ast_log(LOG_DEBUG, "got port of %s\n", pgdbport);
662  ast_log(LOG_DEBUG, "got user of %s\n", pgdbuser);
663  ast_log(LOG_DEBUG, "got dbname of %s\n", pgdbname);
664  ast_log(LOG_DEBUG, "got password of %s\n", pgpassword);
665  ast_log(LOG_DEBUG, "got application name of %s\n", pgappname);
666  ast_log(LOG_DEBUG, "got sql table name of %s\n", table);
667  ast_log(LOG_DEBUG, "got encoding of %s\n", encoding);
668  ast_log(LOG_DEBUG, "got timezone of %s\n", tz);
669  }
670 
671  pgsql_reconnect();
672 
673  if (PQstatus(conn) != CONNECTION_BAD) {
674  char sqlcmd[768];
675  char *fname, *ftype, *flen, *fnotnull, *fdef;
676  int i, rows, version;
677  ast_debug(1, "Successfully connected to PostgreSQL database.\n");
678  connected = 1;
679  connect_time = time(NULL);
680  records = 0;
681  if (PQsetClientEncoding(conn, encoding)) {
682 #ifdef HAVE_PGSQL_pg_encoding_to_char
683  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
684 #else
685  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
686 #endif
687  }
688  version = PQserverVersion(conn);
689 
691  char *schemaname, *tablename, *tmp_schemaname, *tmp_tablename;
692  if (strchr(table, '.')) {
693  tmp_schemaname = ast_strdupa(table);
694  tmp_tablename = strchr(tmp_schemaname, '.');
695  *tmp_tablename++ = '\0';
696  } else {
697  tmp_schemaname = "";
698  tmp_tablename = table;
699  }
700  tablename = ast_alloca(strlen(tmp_tablename) * 2 + 1);
701  PQescapeStringConn(conn, tablename, tmp_tablename, strlen(tmp_tablename), NULL);
702 
703  schemaname = ast_alloca(strlen(tmp_schemaname) * 2 + 1);
704  PQescapeStringConn(conn, schemaname, tmp_schemaname, strlen(tmp_schemaname), NULL);
705 
706  snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, pg_catalog.pg_get_expr(d.adbin, d.adrelid) adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
707  tablename,
708  ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
709  } else {
710  snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", table);
711  }
712  /* Query the columns */
713  result = PQexec(conn, sqlcmd);
714  if (PQresultStatus(result) != PGRES_TUPLES_OK) {
715  pgerror = PQresultErrorMessage(result);
716  ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
717  PQclear(result);
718  unload_module();
721  }
722 
723  rows = PQntuples(result);
724  if (rows == 0) {
725  ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
726  PQclear(result);
727  unload_module();
730  }
731 
732  /* Clear out the columns list. */
733  empty_columns();
734 
735  for (i = 0; i < rows; i++) {
736  fname = PQgetvalue(result, i, 0);
737  ftype = PQgetvalue(result, i, 1);
738  flen = PQgetvalue(result, i, 2);
739  fnotnull = PQgetvalue(result, i, 3);
740  fdef = PQgetvalue(result, i, 4);
741  if (atoi(flen) == -1) {
742  /* For varchar columns, the maximum length is encoded in a different field */
743  flen = PQgetvalue(result, i, 5);
744  }
745 
746  cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
747  if (cur) {
748  sscanf(flen, "%30d", &cur->len);
749  cur->name = (char *)cur + sizeof(*cur);
750  cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
751  strcpy(cur->name, fname);
752  strcpy(cur->type, ftype);
753  if (*fnotnull == 't') {
754  cur->notnull = 1;
755  } else {
756  cur->notnull = 0;
757  }
758  if (!ast_strlen_zero(fdef)) {
759  cur->hasdefault = 1;
760  } else {
761  cur->hasdefault = 0;
762  }
766  }
767  }
768  PQclear(result);
769  } else {
770  pgerror = PQerrorMessage(conn);
771  ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
772  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
773  connected = 0;
774  PQfinish(conn);
775  conn = NULL;
776  }
777 
778  ast_config_destroy(cfg);
779 
781  return 0;
782 }
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
static void empty_columns(void)
Definition: cdr_pgsql.c:482
static char * pgdbuser
Definition: cdr_pgsql.c:65
static void pgsql_reconnect(void)
Definition: cdr_pgsql.c:170
static char * pgappname
Definition: cdr_pgsql.c:67
static int connected
Definition: cdr_pgsql.c:73
#define PGSQL_MIN_VERSION_SCHEMA
Definition: cdr_pgsql.c:58
static const char config[]
Definition: cdr_pgsql.c:61
static ast_mutex_t pgsql_lock
Definition: cdr_pgsql.c:85
static char * pgdbport
Definition: cdr_pgsql.c:68
static char * pgdbname
Definition: cdr_pgsql.c:64
static int records
Definition: cdr_pgsql.c:78
static char * table
Definition: cdr_pgsql.c:69
static char * encoding
Definition: cdr_pgsql.c:70
static char * pghostname
Definition: cdr_pgsql.c:63
static time_t connect_time
Definition: cdr_pgsql.c:76
static char * pgpassword
Definition: cdr_pgsql.c:66
static int unload_module(void)
Definition: cdr_pgsql.c:493
static int reload(void)
Definition: cdr_pgsql.c:794
static PGconn * conn
Definition: cdr_pgsql.c:87
static char * tz
Definition: cdr_pgsql.c:71
static PGresult * result
Definition: cel_pgsql.c:84
static char version[AST_MAX_EXTENSION]
Definition: chan_ooh323.c:391
#define ast_config_load(filename, flags)
Load a config file.
@ CONFIG_FLAG_FILEUNCHANGED
#define CONFIG_STATUS_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:768
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define DEBUG_ATLEAST(level)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_DEBUG
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:741
#define ast_mutex_unlock(a)
Definition: lock.h:188
#define ast_mutex_lock(a)
Definition: lock.h:187
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
#define NULL
Definition: resample.c:96
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Structure used to handle boolean flags.
Definition: utils.h:199
unsigned int hasdefault
Definition: cdr_pgsql.c:94
unsigned int notnull
Definition: cdr_pgsql.c:93

References ast_alloca, ast_calloc, ast_config_destroy(), ast_config_load, ast_debug, ast_free, ast_log, AST_MODULE_LOAD_DECLINE, ast_mutex_lock, ast_mutex_unlock, AST_RWLIST_INSERT_TAIL, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, ast_strdup, ast_strdupa, ast_strlen_zero(), ast_variable_browse(), ast_variable_retrieve(), config, CONFIG_FLAG_FILEUNCHANGED, CONFIG_STATUS_FILEINVALID, CONFIG_STATUS_FILEUNCHANGED, conn, connect_time, connected, DEBUG_ATLEAST, empty_columns(), encoding, columns::hasdefault, columns::len, LOG_DEBUG, LOG_ERROR, LOG_NOTICE, LOG_WARNING, columns::name, columns::notnull, NULL, pgappname, pgdbname, pgdbport, pgdbuser, pghostname, pgpassword, pgsql_lock, PGSQL_MIN_VERSION_SCHEMA, pgsql_reconnect(), records, reload(), result, table, tmp(), columns::type, tz, unload_module(), and version.

Referenced by load_module().

◆ empty_columns()

static void empty_columns ( void  )
static

Definition at line 482 of file cdr_pgsql.c.

483 {
484  struct columns *current;
487  ast_free(current);
488  }
490 
491 }
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:844
size_t current
Definition: main/cli.c:113
struct columns::@4 list

References ast_free, AST_RWLIST_REMOVE_HEAD, AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, current, and columns::list.

Referenced by config_module(), and unload_module().

◆ handle_cdr_pgsql_status()

static char * handle_cdr_pgsql_status ( struct ast_cli_entry e,
int  cmd,
struct ast_cli_args a 
)
static

Handle the CLI command cdr show pgsql status.

Definition at line 121 of file cdr_pgsql.c.

122 {
123  switch (cmd) {
124  case CLI_INIT:
125  e->command = "cdr show pgsql status";
126  e->usage =
127  "Usage: cdr show pgsql status\n"
128  " Shows current connection status for cdr_pgsql\n";
129  return NULL;
130  case CLI_GENERATE:
131  return NULL;
132  }
133 
134  if (a->argc != e->args)
135  return CLI_SHOWUSAGE;
136 
137  if (connected) {
138  char status[256];
139  char status2[100] = "";
140  char buf[362]; /* 256+100+" for "+NULL */
141  int ctime = time(NULL) - connect_time;
142 
143  if (pgdbport) {
144  snprintf(status, 255, "Connected to %s@%s, port %s", pgdbname, pghostname, pgdbport);
145  } else {
146  snprintf(status, 255, "Connected to %s@%s", pgdbname, pghostname);
147  }
148 
149  if (pgdbuser && *pgdbuser) {
150  snprintf(status2, 99, " with username %s", pgdbuser);
151  }
152  if (table && *table) {
153  snprintf(status2, 99, " using table %s", table);
154  }
155 
156  snprintf(buf, sizeof(buf), "%s%s for ", status, status2);
158 
159  if (records == totalrecords) {
160  ast_cli(a->fd, " Wrote %d records since last restart.\n", totalrecords);
161  } else {
162  ast_cli(a->fd, " Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
163  }
164  } else {
165  ast_cli(a->fd, "Not currently connected to a PgSQL server.\n");
166  }
167  return CLI_SUCCESS;
168 }
jack_status_t status
Definition: app_jack.c:146
static int totalrecords
Definition: cdr_pgsql.c:77
#define CLI_SHOWUSAGE
Definition: cli.h:45
void ast_cli_print_timestr_fromseconds(int fd, int seconds, const char *prefix)
Print on cli a duration in seconds in format s year(s), s week(s), s day(s), s hour(s),...
Definition: main/cli.c:3055
#define CLI_SUCCESS
Definition: cli.h:44
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
int args
This gets set in ast_cli_register()
Definition: cli.h:185
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
static struct test_val a

References a, ast_cli_entry::args, ast_cli(), ast_cli_print_timestr_fromseconds(), buf, CLI_GENERATE, CLI_INIT, CLI_SHOWUSAGE, CLI_SUCCESS, ast_cli_entry::command, connect_time, connected, NULL, pgdbname, pgdbport, pgdbuser, pghostname, records, status, table, totalrecords, and ast_cli_entry::usage.

◆ load_module()

static int load_module ( void  )
static

Definition at line 784 of file cdr_pgsql.c.

785 {
787  if (config_module(0)) {
789  }
792 }
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2965
static int config_module(int reload)
Definition: cdr_pgsql.c:520
static const char name[]
Definition: cdr_pgsql.c:60
static int pgsql_log(struct ast_cdr *cdr)
Definition: cdr_pgsql.c:210
static struct ast_cli_entry cdr_pgsql_status_cli[]
Definition: cdr_pgsql.c:81
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
descriptor for a cli entry.
Definition: cli.h:171
const char * description
Definition: module.h:352

References ast_cdr_register(), ast_cli_register_multiple, AST_MODULE_LOAD_DECLINE, cdr_pgsql_status_cli, config_module(), ast_module_info::description, name, and pgsql_log().

◆ pgsql_log()

static int pgsql_log ( struct ast_cdr cdr)
static

Definition at line 210 of file cdr_pgsql.c.

211 {
212  struct ast_tm tm;
213  char *pgerror;
214  PGresult *result;
215  int res = -1;
216 
218 
219  if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
220  pgsql_reconnect();
221 
222  if (PQstatus(conn) != CONNECTION_BAD) {
223  connected = 1;
224  connect_time = time(NULL);
225  records = 0;
226  if (PQsetClientEncoding(conn, encoding)) {
227 #ifdef HAVE_PGSQL_pg_encoding_to_char
228  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
229 #else
230  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
231 #endif
232  }
233  } else {
234  pgerror = PQerrorMessage(conn);
235  ast_log(LOG_ERROR, "Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
236  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
237  PQfinish(conn);
238  conn = NULL;
239  }
240  }
241 
242  if (connected) {
243  struct columns *cur;
244  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
245  char buf[257];
246  char *escapebuf = NULL, *value;
247  char *separator = "";
248  size_t bufsize = 513;
249 
250  escapebuf = ast_malloc(bufsize);
251  if (!escapebuf || !sql || !sql2) {
252  goto ast_log_cleanup;
253  }
254 
255  ast_str_set(&sql, 0, "INSERT INTO %s (", table);
256  ast_str_set(&sql2, 0, " VALUES (");
257 
259  AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
260  /* For fields not set, simply skip them */
261  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
262  if (strcmp(cur->name, "calldate") == 0 && !value) {
263  ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0);
264  }
265  if (!value) {
266  if (cur->notnull && !cur->hasdefault) {
267  /* Field is NOT NULL (but no default), must include it anyway */
268  LENGTHEN_BUF1(strlen(cur->name) + 2);
269  ast_str_append(&sql, 0, "%s\"%s\"", separator, cur->name);
270  LENGTHEN_BUF2(3);
271  ast_str_append(&sql2, 0, "%s''", separator);
272  separator = ", ";
273  }
274  continue;
275  }
276 
277  LENGTHEN_BUF1(strlen(cur->name) + 2);
278  ast_str_append(&sql, 0, "%s\"%s\"", separator, cur->name);
279 
280  if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
281  if (strncmp(cur->type, "int", 3) == 0) {
282  LENGTHEN_BUF2(13);
283  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->start.tv_sec);
284  } else if (strncmp(cur->type, "float", 5) == 0) {
285  LENGTHEN_BUF2(31);
286  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
287  } else {
288  /* char, hopefully */
289  LENGTHEN_BUF2(31);
290  ast_localtime(&cdr->start, &tm, tz);
291  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
292  ast_str_append(&sql2, 0, "%s%s", separator, buf);
293  }
294  } else if (strcmp(cur->name, "answer") == 0) {
295  if (strncmp(cur->type, "int", 3) == 0) {
296  LENGTHEN_BUF2(13);
297  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->answer.tv_sec);
298  } else if (strncmp(cur->type, "float", 5) == 0) {
299  LENGTHEN_BUF2(31);
300  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
301  } else {
302  /* char, hopefully */
303  LENGTHEN_BUF2(31);
304  ast_localtime(&cdr->answer, &tm, tz);
305  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
306  ast_str_append(&sql2, 0, "%s%s", separator, buf);
307  }
308  } else if (strcmp(cur->name, "end") == 0) {
309  if (strncmp(cur->type, "int", 3) == 0) {
310  LENGTHEN_BUF2(13);
311  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->end.tv_sec);
312  } else if (strncmp(cur->type, "float", 5) == 0) {
313  LENGTHEN_BUF2(31);
314  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
315  } else {
316  /* char, hopefully */
317  LENGTHEN_BUF2(31);
318  ast_localtime(&cdr->end, &tm, tz);
319  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
320  ast_str_append(&sql2, 0, "%s%s", separator, buf);
321  }
322  } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
323  if (cur->type[0] == 'i') {
324  /* Get integer, no need to escape anything */
325  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
326  LENGTHEN_BUF2(13);
327  ast_str_append(&sql2, 0, "%s%s", separator, value);
328  } else if (strncmp(cur->type, "float", 5) == 0) {
329  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
330  LENGTHEN_BUF2(31);
331  ast_str_append(&sql2, 0, "%s%f", separator, (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
332  } else {
333  /* Char field, probably */
334  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
335  LENGTHEN_BUF2(31);
336  ast_str_append(&sql2, 0, "%s'%f'", separator, (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
337  }
338  } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
339  if (strncmp(cur->type, "int", 3) == 0) {
340  /* Integer, no need to escape anything */
341  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1);
342  LENGTHEN_BUF2(13);
343  ast_str_append(&sql2, 0, "%s%s", separator, value);
344  } else {
345  /* Although this is a char field, there are no special characters in the values for these fields */
346  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
347  LENGTHEN_BUF2(31);
348  ast_str_append(&sql2, 0, "%s'%s'", separator, value);
349  }
350  } else {
351  /* Arbitrary field, could be anything */
352  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
353  if (strncmp(cur->type, "int", 3) == 0) {
354  long long whatever;
355  if (value && sscanf(value, "%30lld", &whatever) == 1) {
356  LENGTHEN_BUF2(26);
357  ast_str_append(&sql2, 0, "%s%lld", separator, whatever);
358  } else {
359  LENGTHEN_BUF2(2);
360  ast_str_append(&sql2, 0, "%s0", separator);
361  }
362  } else if (strncmp(cur->type, "float", 5) == 0) {
363  long double whatever;
364  if (value && sscanf(value, "%30Lf", &whatever) == 1) {
365  LENGTHEN_BUF2(51);
366  ast_str_append(&sql2, 0, "%s%30Lf", separator, whatever);
367  } else {
368  LENGTHEN_BUF2(2);
369  ast_str_append(&sql2, 0, "%s0", separator);
370  }
371  /* XXX Might want to handle dates, times, and other misc fields here XXX */
372  } else {
373  if (value) {
374  size_t required_size = strlen(value) * 2 + 1;
375 
376  /* If our argument size exceeds our buffer, grow it,
377  * as PQescapeStringConn() expects the buffer to be
378  * adequitely sized and does *NOT* do size checking.
379  */
380  if (required_size > bufsize) {
381  char *tmpbuf = ast_realloc(escapebuf, required_size);
382 
383  if (!tmpbuf) {
385  goto ast_log_cleanup;
386  }
387 
388  escapebuf = tmpbuf;
389  bufsize = required_size;
390  }
391  PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
392  } else {
393  escapebuf[0] = '\0';
394  }
395  LENGTHEN_BUF2(strlen(escapebuf) + 3);
396  ast_str_append(&sql2, 0, "%s'%s'", separator, escapebuf);
397  }
398  }
399  separator = ", ";
400  }
401 
402  LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
404  ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
405 
406  ast_debug(3, "Inserting a CDR record: [%s]\n", ast_str_buffer(sql));
407 
408  /* Test to be sure we're still connected... */
409  /* If we're connected, and connection is working, good. */
410  /* Otherwise, attempt reconnect. If it fails... sorry... */
411  if (PQstatus(conn) == CONNECTION_OK) {
412  connected = 1;
413  } else {
414  ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
415  PQreset(conn);
416  if (PQstatus(conn) == CONNECTION_OK) {
417  ast_log(LOG_ERROR, "Connection reestablished.\n");
418  connected = 1;
419  connect_time = time(NULL);
420  records = 0;
421  } else {
422  pgerror = PQerrorMessage(conn);
423  ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
424  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
425  PQfinish(conn);
426  conn = NULL;
427  connected = 0;
428  goto ast_log_cleanup;
429  }
430  }
431  result = PQexec(conn, ast_str_buffer(sql));
432  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
433  pgerror = PQresultErrorMessage(result);
434  ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
435  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
436  ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
437  PQreset(conn);
438  if (PQstatus(conn) == CONNECTION_OK) {
439  ast_log(LOG_ERROR, "Connection reestablished.\n");
440  connected = 1;
441  connect_time = time(NULL);
442  records = 0;
443  PQclear(result);
444  result = PQexec(conn, ast_str_buffer(sql));
445  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
446  pgerror = PQresultErrorMessage(result);
447  ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
448  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
449  } else {
450  /* Second try worked out ok */
451  totalrecords++;
452  records++;
453  res = 0;
454  }
455  }
456  } else {
457  totalrecords++;
458  records++;
459  res = 0;
460  }
461  PQclear(result);
462 
463  /* Next time, just allocate buffers that are that big to start with. */
464  if (ast_str_strlen(sql) > maxsize) {
465  maxsize = ast_str_strlen(sql);
466  }
467  if (ast_str_strlen(sql2) > maxsize2) {
468  maxsize2 = ast_str_strlen(sql2);
469  }
470 
471 ast_log_cleanup:
472  ast_free(escapebuf);
473  ast_free(sql);
474  ast_free(sql2);
475  }
476 
478  return res;
479 }
#define ast_realloc(p, len)
A wrapper for realloc()
Definition: astmm.h:226
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw)
Format a CDR variable from an already posted CDR.
Definition: cdr.c:3072
#define LENGTHEN_BUF2(size)
Definition: cdr_pgsql.c:117
static int maxsize2
Definition: cdr_pgsql.c:75
static int maxsize
Definition: cdr_pgsql.c:75
#define DATE_FORMAT
Definition: cdr_pgsql.c:56
#define LENGTHEN_BUF1(size)
Definition: cdr_pgsql.c:115
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
#define AST_RWLIST_TRAVERSE
Definition: linkedlists.h:494
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1117
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:739
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:640
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1091
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:711
struct timeval answer
Definition: cdr.h:297
struct timeval start
Definition: cdr.h:295
struct timeval end
Definition: cdr.h:299
Support for dynamic strings.
Definition: strings.h:604
int value
Definition: syslog.c:37
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:85
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:115

References ast_cdr::answer, ast_cdr_format_var(), ast_debug, ast_free, ast_localtime(), ast_log, ast_malloc, ast_mutex_lock, ast_mutex_unlock, ast_realloc, AST_RWLIST_RDLOCK, AST_RWLIST_TRAVERSE, AST_RWLIST_UNLOCK, ast_str_append(), ast_str_buffer(), ast_str_create, ast_str_set(), ast_str_strlen(), ast_strftime(), ast_tvdiff_us(), ast_tvzero(), buf, conn, connect_time, connected, DATE_FORMAT, encoding, ast_cdr::end, columns::hasdefault, LENGTHEN_BUF1, LENGTHEN_BUF2, LOG_ERROR, LOG_WARNING, maxsize, maxsize2, columns::name, columns::notnull, NULL, pgdbname, pgdbuser, pghostname, pgpassword, pgsql_lock, pgsql_reconnect(), records, result, ast_cdr::start, table, totalrecords, columns::type, tz, and value.

Referenced by load_module().

◆ pgsql_reconnect()

static void pgsql_reconnect ( void  )
static

Definition at line 170 of file cdr_pgsql.c.

171 {
172  struct ast_str *conn_info = ast_str_create(128);
173  if (!conn_info) {
174  ast_log(LOG_ERROR, "Failed to allocate memory for connection string.\n");
175  return;
176  }
177 
178  if (conn) {
179  PQfinish(conn);
180  conn = NULL;
181  }
182 
183  if (!ast_strlen_zero(pghostname)) {
184  ast_str_append(&conn_info, 0, "host=%s ", pghostname);
185  }
186  if (!ast_strlen_zero(pgdbport)) {
187  ast_str_append(&conn_info, 0, "port=%s ", pgdbport);
188  }
189  if (!ast_strlen_zero(pgdbname)) {
190  ast_str_append(&conn_info, 0, "dbname=%s ", pgdbname);
191  }
192  if (!ast_strlen_zero(pgdbuser)) {
193  ast_str_append(&conn_info, 0, "user=%s ", pgdbuser);
194  }
195  if (!ast_strlen_zero(pgappname)) {
196  ast_str_append(&conn_info, 0, "application_name=%s ", pgappname);
197  }
198  if (!ast_strlen_zero(pgpassword)) {
199  ast_str_append(&conn_info, 0, "password=%s", pgpassword);
200  }
201  if (ast_str_strlen(conn_info) == 0) {
202  ast_log(LOG_ERROR, "Connection string is blank.\n");
203  return;
204  }
205 
206  conn = PQconnectdb(ast_str_buffer(conn_info));
207  ast_free(conn_info);
208 }

References ast_free, ast_log, ast_str_append(), ast_str_buffer(), ast_str_create, ast_str_strlen(), ast_strlen_zero(), conn, LOG_ERROR, NULL, pgappname, pgdbname, pgdbport, pgdbuser, pghostname, and pgpassword.

Referenced by config_module(), and pgsql_log().

◆ reload()

static int reload ( void  )
static

Definition at line 794 of file cdr_pgsql.c.

795 {
796  return config_module(1);
797 }

Referenced by config_module().

◆ unload_module()

static int unload_module ( void  )
static

Definition at line 493 of file cdr_pgsql.c.

494 {
495  if (ast_cdr_unregister(name)) {
496  return -1;
497  }
498 
500 
501  if (conn) {
502  PQfinish(conn);
503  conn = NULL;
504  }
511  ast_free(table);
513  ast_free(tz);
514 
515  empty_columns();
516 
517  return 0;
518 }
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:3010
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define ARRAY_LEN(a)
Definition: utils.h:661

References ARRAY_LEN, ast_cdr_unregister(), ast_cli_unregister_multiple(), ast_free, cdr_pgsql_status_cli, conn, empty_columns(), encoding, name, NULL, pgappname, pgdbname, pgdbport, pgdbuser, pghostname, pgpassword, table, and tz.

Referenced by config_module().

Variable Documentation

◆ __mod_info

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "PostgreSQL CDR Backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, .requires = "cdr", }
static

Definition at line 794 of file cdr_pgsql.c.

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 806 of file cdr_pgsql.c.

◆ cdr_pgsql_status_cli

struct ast_cli_entry cdr_pgsql_status_cli[]
static
Initial value:
= {
{ .handler = handle_cdr_pgsql_status , .summary = "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)" ,},
}
static char * handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Handle the CLI command cdr show pgsql status.
Definition: cdr_pgsql.c:121

Definition at line 80 of file cdr_pgsql.c.

Referenced by load_module(), and unload_module().

◆ config

const char config[] = "cdr_pgsql.conf"
static

Definition at line 61 of file cdr_pgsql.c.

Referenced by config_module().

◆ conn

PGconn* conn = NULL
static

◆ connect_time

time_t connect_time = 0
static

Definition at line 76 of file cdr_pgsql.c.

Referenced by config_module(), handle_cdr_pgsql_status(), and pgsql_log().

◆ connected

int connected = 0
static

Definition at line 73 of file cdr_pgsql.c.

Referenced by config_module(), handle_cdr_pgsql_status(), and pgsql_log().

◆ encoding

char* encoding
static

Definition at line 70 of file cdr_pgsql.c.

Referenced by check_header(), config_module(), custom_prepare(), pgsql_log(), and unload_module().

◆ maxsize

int maxsize = 512
static

Definition at line 75 of file cdr_pgsql.c.

Referenced by pgsql_log().

◆ maxsize2

int maxsize2 = 512
static

Definition at line 75 of file cdr_pgsql.c.

Referenced by pgsql_log().

◆ name

const char name[] = "pgsql"
static

Definition at line 60 of file cdr_pgsql.c.

Referenced by load_module(), and unload_module().

◆ pgappname

char* pgappname
static

Definition at line 67 of file cdr_pgsql.c.

Referenced by config_module(), pgsql_reconnect(), and unload_module().

◆ pgdbname

char* pgdbname
static

◆ pgdbport

char* pgdbport
static

Definition at line 68 of file cdr_pgsql.c.

Referenced by config_module(), handle_cdr_pgsql_status(), pgsql_reconnect(), and unload_module().

◆ pgdbuser

char* pgdbuser
static

◆ pghostname

char* pghostname
static

◆ pgpassword

char* pgpassword
static

Definition at line 66 of file cdr_pgsql.c.

Referenced by config_module(), pgsql_log(), pgsql_reconnect(), and unload_module().

◆ pgsql_lock

ast_mutex_t pgsql_lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, {1, 0} }
static

Definition at line 85 of file cdr_pgsql.c.

Referenced by config_module(), and pgsql_log().

◆ psql_columns

struct psql_columns psql_columns = { .first = NULL, .last = NULL, .lock = { PTHREAD_RWLOCK_INITIALIZER , NULL, {1, 0} } , }
static

◆ records

int records
static

◆ table

char* table
static

Definition at line 69 of file cdr_pgsql.c.

Referenced by config_module(), handle_cdr_pgsql_status(), pgsql_log(), and unload_module().

◆ totalrecords

int totalrecords = 0
static

Definition at line 77 of file cdr_pgsql.c.

Referenced by handle_cdr_pgsql_status(), and pgsql_log().

◆ tz

char* tz
static