Asterisk - The Open Source Telephony Project  GIT-master-a24979a
cdr_pgsql.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2003 - 2012
5  *
6  * Matthew D. Hardeman <mhardemn@papersoft.com>
7  * Adapted from the MySQL CDR logger originally by James Sharp
8  *
9  * Modified September 2003
10  * Matthew D. Hardeman <mhardemn@papersoft.com>
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  * This program is free software, distributed under the terms of
19  * the GNU General Public License Version 2. See the LICENSE file
20  * at the top of the source tree.
21  */
22 
23 /*!
24  * \file
25  * \brief PostgreSQL CDR logger
26  *
27  * \author Matthew D. Hardeman <mhardemn@papersoft.com>
28  * PostgreSQL http://www.postgresql.org/
29  * \ingroup cdr_drivers
30  */
31 
32 /*! \li \ref cdr_pgsql.c uses the configuration file \ref cdr_pgsql.conf
33  * \addtogroup configuration_file Configuration Files
34  */
35 
36 /*!
37  * \page cdr_pgsql.conf cdr_pgsql.conf
38  * \verbinclude cdr_pgsql.conf.sample
39  */
40 
41 /*** MODULEINFO
42  <depend>pgsql</depend>
43  <support_level>extended</support_level>
44  ***/
45 
46 #include "asterisk.h"
47 
48 #include <libpq-fe.h>
49 
50 #include "asterisk/config.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/cdr.h"
53 #include "asterisk/cli.h"
54 #include "asterisk/module.h"
55 
56 #define DATE_FORMAT "'%Y-%m-%d %T'"
57 
58 #define PGSQL_MIN_VERSION_SCHEMA 70300
59 
60 static const char name[] = "pgsql";
61 static const char config[] = "cdr_pgsql.conf";
62 
63 static char *pghostname;
64 static char *pgdbname;
65 static char *pgdbuser;
66 static char *pgpassword;
67 static char *pgappname;
68 static char *pgdbport;
69 static char *table;
70 static char *encoding;
71 static char *tz;
72 
73 static int connected = 0;
74 /* Optimization to reduce number of memory allocations */
75 static int maxsize = 512, maxsize2 = 512;
76 static time_t connect_time = 0;
77 static int totalrecords = 0;
78 static int records;
79 
80 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
81 static struct ast_cli_entry cdr_pgsql_status_cli[] = {
82  AST_CLI_DEFINE(handle_cdr_pgsql_status, "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)"),
83 };
84 
86 
87 static PGconn *conn = NULL;
88 
89 struct columns {
90  char *name;
91  char *type;
92  int len;
93  unsigned int notnull:1;
94  unsigned int hasdefault:1;
96 };
97 
99 
100 #define LENGTHEN_BUF(size, var_sql) \
101  do { \
102  /* Lengthen buffer, if necessary */ \
103  if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
104  if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 3) / 512 + 1) * 512) != 0) { \
105  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", pghostname, table); \
106  ast_free(sql); \
107  ast_free(sql2); \
108  AST_RWLIST_UNLOCK(&psql_columns); \
109  ast_mutex_unlock(&pgsql_lock); \
110  return -1; \
111  } \
112  } \
113  } while (0)
114 
115 #define LENGTHEN_BUF1(size) \
116  LENGTHEN_BUF(size, sql);
117 #define LENGTHEN_BUF2(size) \
118  LENGTHEN_BUF(size, sql2);
119 
120 /*! \brief Handle the CLI command cdr show pgsql status */
121 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
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 }
169 
170 static void pgsql_reconnect(void)
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 }
209 
210 static int pgsql_log(struct ast_cdr *cdr)
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 }
480 
481 /* This function should be called without holding the pgsql_columns lock */
482 static void empty_columns(void)
483 {
484  struct columns *current;
487  ast_free(current);
488  }
490 
491 }
492 
493 static int unload_module(void)
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 }
519 
520 static int config_module(int reload)
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 }
783 
784 static int load_module(void)
785 {
787  if (config_module(0)) {
789  }
792 }
793 
794 static int reload(void)
795 {
796  return config_module(1);
797 }
798 
799 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
800  .support_level = AST_MODULE_SUPPORT_EXTENDED,
801  .load = load_module,
802  .unload = unload_module,
803  .reload = reload,
804  .load_pri = AST_MODPRI_CDR_DRIVER,
805  .requires = "cdr",
806 );
jack_status_t status
Definition: app_jack.c:146
Asterisk main include file. File version handling, generic pbx functions.
#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_realloc(p, len)
A wrapper for realloc()
Definition: astmm.h:226
#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_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
Call Detail Record API.
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
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:3010
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2965
static void empty_columns(void)
Definition: cdr_pgsql.c:482
static int config_module(int reload)
Definition: cdr_pgsql.c:520
static const char name[]
Definition: cdr_pgsql.c:60
#define LENGTHEN_BUF2(size)
Definition: cdr_pgsql.c:117
static char * pgdbuser
Definition: cdr_pgsql.c:65
static int maxsize2
Definition: cdr_pgsql.c:75
static int maxsize
Definition: cdr_pgsql.c:75
static void pgsql_reconnect(void)
Definition: cdr_pgsql.c:170
static char * pgappname
Definition: cdr_pgsql.c:67
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
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 int load_module(void)
Definition: cdr_pgsql.c:784
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 int totalrecords
Definition: cdr_pgsql.c:77
#define DATE_FORMAT
Definition: cdr_pgsql.c:56
static PGconn * conn
Definition: cdr_pgsql.c:87
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 LENGTHEN_BUF1(size)
Definition: cdr_pgsql.c:115
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
General Asterisk PBX channel definitions.
Standard Command Line Interface.
#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
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Configuration File Parser.
#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_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
#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_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized.
Definition: linkedlists.h:333
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:844
#define AST_RWLIST_TRAVERSE
Definition: linkedlists.h:494
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:741
#define AST_RWLIST_ENTRY
Definition: linkedlists.h:415
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
#define ast_mutex_unlock(a)
Definition: lock.h:188
#define ast_mutex_lock(a)
Definition: lock.h:187
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
size_t current
Definition: main/cli.c:113
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_CDR_DRIVER
Definition: module.h:331
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ 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
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
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#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
Responsible for call detail data.
Definition: cdr.h:277
struct timeval answer
Definition: cdr.h:297
struct timeval start
Definition: cdr.h:295
struct timeval end
Definition: cdr.h:299
descriptor for a cli entry.
Definition: cli.h:171
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
Structure used to handle boolean flags.
Definition: utils.h:199
const char * description
Definition: module.h:352
Support for dynamic strings.
Definition: strings.h:604
unsigned int hasdefault
Definition: cdr_pgsql.c:94
struct columns::@4 list
unsigned int notnull
Definition: cdr_pgsql.c:93
int value
Definition: syslog.c:37
static struct test_val a
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
#define ARRAY_LEN(a)
Definition: utils.h:661