Asterisk - The Open Source Telephony Project  18.5.0
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 <[email protected]>
7  * Adapted from the MySQL CDR logger originally by James Sharp
8  *
9  * Modified September 2003
10  * Matthew D. Hardeman <[email protected]>
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 <[email protected]>
28  * PostgreSQL http://www.postgresql.org/
29  *
30  * See also
31  * \arg \ref Config_cdr
32  * PostgreSQL http://www.postgresql.org/
33  * \ingroup cdr_drivers
34  */
35 
36 /*! \li \ref cdr_pgsql.c uses the configuration file \ref cdr_pgsql.conf
37  * \addtogroup configuration_file Configuration Files
38  */
39 
40 /*!
41  * \page cdr_pgsql.conf cdr_pgsql.conf
42  * \verbinclude cdr_pgsql.conf.sample
43  */
44 
45 /*** MODULEINFO
46  <depend>pgsql</depend>
47  <support_level>extended</support_level>
48  ***/
49 
50 #include "asterisk.h"
51 
52 #include <libpq-fe.h>
53 
54 #include "asterisk/config.h"
55 #include "asterisk/channel.h"
56 #include "asterisk/cdr.h"
57 #include "asterisk/cli.h"
58 #include "asterisk/module.h"
59 
60 #define DATE_FORMAT "'%Y-%m-%d %T'"
61 
62 #define PGSQL_MIN_VERSION_SCHEMA 70300
63 
64 static const char name[] = "pgsql";
65 static const char config[] = "cdr_pgsql.conf";
66 
67 static char *pghostname;
68 static char *pgdbname;
69 static char *pgdbuser;
70 static char *pgpassword;
71 static char *pgappname;
72 static char *pgdbport;
73 static char *table;
74 static char *encoding;
75 static char *tz;
76 
77 static int connected = 0;
78 /* Optimization to reduce number of memory allocations */
79 static int maxsize = 512, maxsize2 = 512;
80 static time_t connect_time = 0;
81 static int totalrecords = 0;
82 static int records;
83 
84 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
86  AST_CLI_DEFINE(handle_cdr_pgsql_status, "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)"),
87 };
88 
90 
91 static PGconn *conn = NULL;
92 
93 struct columns {
94  char *name;
95  char *type;
96  int len;
97  unsigned int notnull:1;
98  unsigned int hasdefault:1;
100 };
101 
103 
104 #define LENGTHEN_BUF(size, var_sql) \
105  do { \
106  /* Lengthen buffer, if necessary */ \
107  if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
108  if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 3) / 512 + 1) * 512) != 0) { \
109  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", pghostname, table); \
110  ast_free(sql); \
111  ast_free(sql2); \
112  AST_RWLIST_UNLOCK(&psql_columns); \
113  ast_mutex_unlock(&pgsql_lock); \
114  return -1; \
115  } \
116  } \
117  } while (0)
118 
119 #define LENGTHEN_BUF1(size) \
120  LENGTHEN_BUF(size, sql);
121 #define LENGTHEN_BUF2(size) \
122  LENGTHEN_BUF(size, sql2);
123 
124 /*! \brief Handle the CLI command cdr show pgsql status */
125 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
126 {
127  switch (cmd) {
128  case CLI_INIT:
129  e->command = "cdr show pgsql status";
130  e->usage =
131  "Usage: cdr show pgsql status\n"
132  " Shows current connection status for cdr_pgsql\n";
133  return NULL;
134  case CLI_GENERATE:
135  return NULL;
136  }
137 
138  if (a->argc != e->args)
139  return CLI_SHOWUSAGE;
140 
141  if (connected) {
142  char status[256];
143  char status2[100] = "";
144  char buf[362]; /* 256+100+" for "+NULL */
145  int ctime = time(NULL) - connect_time;
146 
147  if (pgdbport) {
148  snprintf(status, 255, "Connected to %s@%s, port %s", pgdbname, pghostname, pgdbport);
149  } else {
150  snprintf(status, 255, "Connected to %s@%s", pgdbname, pghostname);
151  }
152 
153  if (pgdbuser && *pgdbuser) {
154  snprintf(status2, 99, " with username %s", pgdbuser);
155  }
156  if (table && *table) {
157  snprintf(status2, 99, " using table %s", table);
158  }
159 
160  snprintf(buf, sizeof(buf), "%s%s for ", status, status2);
161  ast_cli_print_timestr_fromseconds(a->fd, ctime, buf);
162 
163  if (records == totalrecords) {
164  ast_cli(a->fd, " Wrote %d records since last restart.\n", totalrecords);
165  } else {
166  ast_cli(a->fd, " Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
167  }
168  } else {
169  ast_cli(a->fd, "Not currently connected to a PgSQL server.\n");
170  }
171  return CLI_SUCCESS;
172 }
173 
174 static void pgsql_reconnect(void)
175 {
176  struct ast_str *conn_info = ast_str_create(128);
177  if (!conn_info) {
178  ast_log(LOG_ERROR, "Failed to allocate memory for connection string.\n");
179  return;
180  }
181 
182  if (conn) {
183  PQfinish(conn);
184  conn = NULL;
185  }
186 
187  if (!ast_strlen_zero(pghostname)) {
188  ast_str_append(&conn_info, 0, "host=%s ", pghostname);
189  }
190  if (!ast_strlen_zero(pgdbport)) {
191  ast_str_append(&conn_info, 0, "port=%s ", pgdbport);
192  }
193  if (!ast_strlen_zero(pgdbname)) {
194  ast_str_append(&conn_info, 0, "dbname=%s ", pgdbname);
195  }
196  if (!ast_strlen_zero(pgdbuser)) {
197  ast_str_append(&conn_info, 0, "user=%s ", pgdbuser);
198  }
199  if (!ast_strlen_zero(pgappname)) {
200  ast_str_append(&conn_info, 0, "application_name=%s ", pgappname);
201  }
202  if (!ast_strlen_zero(pgpassword)) {
203  ast_str_append(&conn_info, 0, "password=%s", pgpassword);
204  }
205  if (ast_str_strlen(conn_info) == 0) {
206  ast_log(LOG_ERROR, "Connection string is blank.\n");
207  return;
208  }
209 
210  conn = PQconnectdb(ast_str_buffer(conn_info));
211  ast_free(conn_info);
212 }
213 
214 static int pgsql_log(struct ast_cdr *cdr)
215 {
216  struct ast_tm tm;
217  char *pgerror;
218  PGresult *result;
219  int res = -1;
220 
222 
223  if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
224  pgsql_reconnect();
225 
226  if (PQstatus(conn) != CONNECTION_BAD) {
227  connected = 1;
228  connect_time = time(NULL);
229  records = 0;
230  if (PQsetClientEncoding(conn, encoding)) {
231 #ifdef HAVE_PGSQL_pg_encoding_to_char
232  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
233 #else
234  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
235 #endif
236  }
237  } else {
238  pgerror = PQerrorMessage(conn);
239  ast_log(LOG_ERROR, "Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
240  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
241  PQfinish(conn);
242  conn = NULL;
243  }
244  }
245 
246  if (connected) {
247  struct columns *cur;
248  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
249  char buf[257];
250  char *escapebuf = NULL, *value;
251  char *separator = "";
252  size_t bufsize = 513;
253 
254  escapebuf = ast_malloc(bufsize);
255  if (!escapebuf || !sql || !sql2) {
256  goto ast_log_cleanup;
257  }
258 
259  ast_str_set(&sql, 0, "INSERT INTO %s (", table);
260  ast_str_set(&sql2, 0, " VALUES (");
261 
264  /* For fields not set, simply skip them */
265  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
266  if (strcmp(cur->name, "calldate") == 0 && !value) {
267  ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0);
268  }
269  if (!value) {
270  if (cur->notnull && !cur->hasdefault) {
271  /* Field is NOT NULL (but no default), must include it anyway */
272  LENGTHEN_BUF1(strlen(cur->name) + 2);
273  ast_str_append(&sql, 0, "%s\"%s\"", separator, cur->name);
274  LENGTHEN_BUF2(3);
275  ast_str_append(&sql2, 0, "%s''", separator);
276  separator = ", ";
277  }
278  continue;
279  }
280 
281  LENGTHEN_BUF1(strlen(cur->name) + 2);
282  ast_str_append(&sql, 0, "%s\"%s\"", separator, cur->name);
283 
284  if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
285  if (strncmp(cur->type, "int", 3) == 0) {
286  LENGTHEN_BUF2(13);
287  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->start.tv_sec);
288  } else if (strncmp(cur->type, "float", 5) == 0) {
289  LENGTHEN_BUF2(31);
290  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
291  } else {
292  /* char, hopefully */
293  LENGTHEN_BUF2(31);
294  ast_localtime(&cdr->start, &tm, tz);
295  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
296  ast_str_append(&sql2, 0, "%s%s", separator, buf);
297  }
298  } else if (strcmp(cur->name, "answer") == 0) {
299  if (strncmp(cur->type, "int", 3) == 0) {
300  LENGTHEN_BUF2(13);
301  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->answer.tv_sec);
302  } else if (strncmp(cur->type, "float", 5) == 0) {
303  LENGTHEN_BUF2(31);
304  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
305  } else {
306  /* char, hopefully */
307  LENGTHEN_BUF2(31);
308  ast_localtime(&cdr->answer, &tm, tz);
309  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
310  ast_str_append(&sql2, 0, "%s%s", separator, buf);
311  }
312  } else if (strcmp(cur->name, "end") == 0) {
313  if (strncmp(cur->type, "int", 3) == 0) {
314  LENGTHEN_BUF2(13);
315  ast_str_append(&sql2, 0, "%s%ld", separator, (long) cdr->end.tv_sec);
316  } else if (strncmp(cur->type, "float", 5) == 0) {
317  LENGTHEN_BUF2(31);
318  ast_str_append(&sql2, 0, "%s%f", separator, (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
319  } else {
320  /* char, hopefully */
321  LENGTHEN_BUF2(31);
322  ast_localtime(&cdr->end, &tm, tz);
323  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
324  ast_str_append(&sql2, 0, "%s%s", separator, buf);
325  }
326  } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
327  if (cur->type[0] == 'i') {
328  /* Get integer, no need to escape anything */
329  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
330  LENGTHEN_BUF2(13);
331  ast_str_append(&sql2, 0, "%s%s", separator, value);
332  } else if (strncmp(cur->type, "float", 5) == 0) {
333  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
334  LENGTHEN_BUF2(31);
335  ast_str_append(&sql2, 0, "%s%f", separator, (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
336  } else {
337  /* Char field, probably */
338  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
339  LENGTHEN_BUF2(31);
340  ast_str_append(&sql2, 0, "%s'%f'", separator, (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
341  }
342  } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
343  if (strncmp(cur->type, "int", 3) == 0) {
344  /* Integer, no need to escape anything */
345  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1);
346  LENGTHEN_BUF2(13);
347  ast_str_append(&sql2, 0, "%s%s", separator, value);
348  } else {
349  /* Although this is a char field, there are no special characters in the values for these fields */
350  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
351  LENGTHEN_BUF2(31);
352  ast_str_append(&sql2, 0, "%s'%s'", separator, value);
353  }
354  } else {
355  /* Arbitrary field, could be anything */
356  ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
357  if (strncmp(cur->type, "int", 3) == 0) {
358  long long whatever;
359  if (value && sscanf(value, "%30lld", &whatever) == 1) {
360  LENGTHEN_BUF2(26);
361  ast_str_append(&sql2, 0, "%s%lld", separator, whatever);
362  } else {
363  LENGTHEN_BUF2(2);
364  ast_str_append(&sql2, 0, "%s0", separator);
365  }
366  } else if (strncmp(cur->type, "float", 5) == 0) {
367  long double whatever;
368  if (value && sscanf(value, "%30Lf", &whatever) == 1) {
369  LENGTHEN_BUF2(51);
370  ast_str_append(&sql2, 0, "%s%30Lf", separator, whatever);
371  } else {
372  LENGTHEN_BUF2(2);
373  ast_str_append(&sql2, 0, "%s0", separator);
374  }
375  /* XXX Might want to handle dates, times, and other misc fields here XXX */
376  } else {
377  if (value) {
378  size_t required_size = strlen(value) * 2 + 1;
379 
380  /* If our argument size exceeds our buffer, grow it,
381  * as PQescapeStringConn() expects the buffer to be
382  * adequitely sized and does *NOT* do size checking.
383  */
384  if (required_size > bufsize) {
385  char *tmpbuf = ast_realloc(escapebuf, required_size);
386 
387  if (!tmpbuf) {
389  goto ast_log_cleanup;
390  }
391 
392  escapebuf = tmpbuf;
393  bufsize = required_size;
394  }
395  PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
396  } else {
397  escapebuf[0] = '\0';
398  }
399  LENGTHEN_BUF2(strlen(escapebuf) + 3);
400  ast_str_append(&sql2, 0, "%s'%s'", separator, escapebuf);
401  }
402  }
403  separator = ", ";
404  }
405 
406  LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
408  ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
409 
410  ast_debug(3, "Inserting a CDR record: [%s]\n", ast_str_buffer(sql));
411 
412  /* Test to be sure we're still connected... */
413  /* If we're connected, and connection is working, good. */
414  /* Otherwise, attempt reconnect. If it fails... sorry... */
415  if (PQstatus(conn) == CONNECTION_OK) {
416  connected = 1;
417  } else {
418  ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
419  PQreset(conn);
420  if (PQstatus(conn) == CONNECTION_OK) {
421  ast_log(LOG_ERROR, "Connection reestablished.\n");
422  connected = 1;
423  connect_time = time(NULL);
424  records = 0;
425  } else {
426  pgerror = PQerrorMessage(conn);
427  ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
428  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
429  PQfinish(conn);
430  conn = NULL;
431  connected = 0;
432  goto ast_log_cleanup;
433  }
434  }
435  result = PQexec(conn, ast_str_buffer(sql));
436  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
437  pgerror = PQresultErrorMessage(result);
438  ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
439  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
440  ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
441  PQreset(conn);
442  if (PQstatus(conn) == CONNECTION_OK) {
443  ast_log(LOG_ERROR, "Connection reestablished.\n");
444  connected = 1;
445  connect_time = time(NULL);
446  records = 0;
447  PQclear(result);
448  result = PQexec(conn, ast_str_buffer(sql));
449  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
450  pgerror = PQresultErrorMessage(result);
451  ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
452  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
453  } else {
454  /* Second try worked out ok */
455  totalrecords++;
456  records++;
457  res = 0;
458  }
459  }
460  } else {
461  totalrecords++;
462  records++;
463  res = 0;
464  }
465  PQclear(result);
466 
467  /* Next time, just allocate buffers that are that big to start with. */
468  if (ast_str_strlen(sql) > maxsize) {
469  maxsize = ast_str_strlen(sql);
470  }
471  if (ast_str_strlen(sql2) > maxsize2) {
472  maxsize2 = ast_str_strlen(sql2);
473  }
474 
475 ast_log_cleanup:
476  ast_free(escapebuf);
477  ast_free(sql);
478  ast_free(sql2);
479  }
480 
482  return res;
483 }
484 
485 /* This function should be called without holding the pgsql_columns lock */
486 static void empty_columns(void)
487 {
488  struct columns *current;
490  while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
491  ast_free(current);
492  }
494 
495 }
496 
497 static int unload_module(void)
498 {
499  if (ast_cdr_unregister(name)) {
500  return -1;
501  }
502 
503  ast_cli_unregister_multiple(cdr_pgsql_status_cli, ARRAY_LEN(cdr_pgsql_status_cli));
504 
505  if (conn) {
506  PQfinish(conn);
507  conn = NULL;
508  }
515  ast_free(table);
517  ast_free(tz);
518 
519  empty_columns();
520 
521  return 0;
522 }
523 
524 static int config_module(int reload)
525 {
526  char *pgerror;
527  struct columns *cur;
528  PGresult *result;
529  const char *tmp;
530  struct ast_config *cfg;
531  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
532 
533  if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
534  ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
535  return -1;
536  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
537  return 0;
538  }
539 
541 
542  if (!ast_variable_browse(cfg, "global")) {
543  ast_config_destroy(cfg);
545  ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
546  reload ? "reload" : "load");
547  return -1;
548  }
549 
550  if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
551  ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
552  tmp = ""; /* connect via UNIX-socket by default */
553  }
554 
556  if (!(pghostname = ast_strdup(tmp))) {
557  ast_config_destroy(cfg);
559  return -1;
560  }
561 
562  if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
563  ast_log(LOG_WARNING, "PostgreSQL database not specified. Assuming asterisk\n");
564  tmp = "asteriskcdrdb";
565  }
566 
568  if (!(pgdbname = ast_strdup(tmp))) {
569  ast_config_destroy(cfg);
571  return -1;
572  }
573 
574  if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
575  ast_log(LOG_WARNING, "PostgreSQL database user not specified. Assuming asterisk\n");
576  tmp = "asterisk";
577  }
578 
580  if (!(pgdbuser = ast_strdup(tmp))) {
581  ast_config_destroy(cfg);
583  return -1;
584  }
585 
586  if (!(tmp = ast_variable_retrieve(cfg, "global", "appname"))) {
587  tmp = "";
588  }
589 
591  if (!(pgappname = ast_strdup(tmp))) {
592  ast_config_destroy(cfg);
594  return -1;
595  }
596 
597 
598  if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
599  ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
600  tmp = "";
601  }
602 
604  if (!(pgpassword = ast_strdup(tmp))) {
605  ast_config_destroy(cfg);
607  return -1;
608  }
609 
610  if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
611  ast_log(LOG_WARNING, "PostgreSQL database port not specified. Using default 5432.\n");
612  tmp = "5432";
613  }
614 
616  if (!(pgdbport = ast_strdup(tmp))) {
617  ast_config_destroy(cfg);
619  return -1;
620  }
621 
622  if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
623  ast_log(LOG_WARNING, "CDR table not specified. Assuming cdr\n");
624  tmp = "cdr";
625  }
626 
627  ast_free(table);
628  if (!(table = ast_strdup(tmp))) {
629  ast_config_destroy(cfg);
631  return -1;
632  }
633 
634  if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
635  ast_log(LOG_WARNING, "Encoding not specified. Assuming LATIN9\n");
636  tmp = "LATIN9";
637  }
638 
640  if (!(encoding = ast_strdup(tmp))) {
641  ast_config_destroy(cfg);
643  return -1;
644  }
645 
646  if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
647  tmp = "";
648  }
649 
650  ast_free(tz);
651  tz = NULL;
652 
653  if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
654  ast_config_destroy(cfg);
656  return -1;
657  }
658 
659  if (DEBUG_ATLEAST(1)) {
661  ast_log(LOG_DEBUG, "using default unix socket\n");
662  } else {
663  ast_log(LOG_DEBUG, "got hostname of %s\n", pghostname);
664  }
665  ast_log(LOG_DEBUG, "got port of %s\n", pgdbport);
666  ast_log(LOG_DEBUG, "got user of %s\n", pgdbuser);
667  ast_log(LOG_DEBUG, "got dbname of %s\n", pgdbname);
668  ast_log(LOG_DEBUG, "got password of %s\n", pgpassword);
669  ast_log(LOG_DEBUG, "got application name of %s\n", pgappname);
670  ast_log(LOG_DEBUG, "got sql table name of %s\n", table);
671  ast_log(LOG_DEBUG, "got encoding of %s\n", encoding);
672  ast_log(LOG_DEBUG, "got timezone of %s\n", tz);
673  }
674 
675  pgsql_reconnect();
676 
677  if (PQstatus(conn) != CONNECTION_BAD) {
678  char sqlcmd[768];
679  char *fname, *ftype, *flen, *fnotnull, *fdef;
680  int i, rows, version;
681  ast_debug(1, "Successfully connected to PostgreSQL database.\n");
682  connected = 1;
683  connect_time = time(NULL);
684  records = 0;
685  if (PQsetClientEncoding(conn, encoding)) {
686 #ifdef HAVE_PGSQL_pg_encoding_to_char
687  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
688 #else
689  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
690 #endif
691  }
692  version = PQserverVersion(conn);
693 
694  if (version >= PGSQL_MIN_VERSION_SCHEMA) {
695  char *schemaname, *tablename, *tmp_schemaname, *tmp_tablename;
696  if (strchr(table, '.')) {
697  tmp_schemaname = ast_strdupa(table);
698  tmp_tablename = strchr(tmp_schemaname, '.');
699  *tmp_tablename++ = '\0';
700  } else {
701  tmp_schemaname = "";
702  tmp_tablename = table;
703  }
704  tablename = ast_alloca(strlen(tmp_tablename) * 2 + 1);
705  PQescapeStringConn(conn, tablename, tmp_tablename, strlen(tmp_tablename), NULL);
706 
707  schemaname = ast_alloca(strlen(tmp_schemaname) * 2 + 1);
708  PQescapeStringConn(conn, schemaname, tmp_schemaname, strlen(tmp_schemaname), NULL);
709 
710  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",
711  tablename,
712  ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
713  } else {
714  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);
715  }
716  /* Query the columns */
717  result = PQexec(conn, sqlcmd);
718  if (PQresultStatus(result) != PGRES_TUPLES_OK) {
719  pgerror = PQresultErrorMessage(result);
720  ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
721  PQclear(result);
722  unload_module();
725  }
726 
727  rows = PQntuples(result);
728  if (rows == 0) {
729  ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
730  PQclear(result);
731  unload_module();
734  }
735 
736  /* Clear out the columns list. */
737  empty_columns();
738 
739  for (i = 0; i < rows; i++) {
740  fname = PQgetvalue(result, i, 0);
741  ftype = PQgetvalue(result, i, 1);
742  flen = PQgetvalue(result, i, 2);
743  fnotnull = PQgetvalue(result, i, 3);
744  fdef = PQgetvalue(result, i, 4);
745  if (atoi(flen) == -1) {
746  /* For varchar columns, the maximum length is encoded in a different field */
747  flen = PQgetvalue(result, i, 5);
748  }
749 
750  cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
751  if (cur) {
752  sscanf(flen, "%30d", &cur->len);
753  cur->name = (char *)cur + sizeof(*cur);
754  cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
755  strcpy(cur->name, fname);
756  strcpy(cur->type, ftype);
757  if (*fnotnull == 't') {
758  cur->notnull = 1;
759  } else {
760  cur->notnull = 0;
761  }
762  if (!ast_strlen_zero(fdef)) {
763  cur->hasdefault = 1;
764  } else {
765  cur->hasdefault = 0;
766  }
770  }
771  }
772  PQclear(result);
773  } else {
774  pgerror = PQerrorMessage(conn);
775  ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
776  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
777  connected = 0;
778  PQfinish(conn);
779  conn = NULL;
780  }
781 
782  ast_config_destroy(cfg);
783 
785  return 0;
786 }
787 
788 static int load_module(void)
789 {
790  ast_cli_register_multiple(cdr_pgsql_status_cli, sizeof(cdr_pgsql_status_cli) / sizeof(struct ast_cli_entry));
791  if (config_module(0)) {
793  }
796 }
797 
798 static int reload(void)
799 {
800  return config_module(1);
801 }
802 
803 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
804  .support_level = AST_MODULE_SUPPORT_EXTENDED,
805  .load = load_module,
806  .unload = unload_module,
807  .reload = reload,
808  .load_pri = AST_MODPRI_CDR_DRIVER,
809  .requires = "cdr",
810 );
unsigned int hasdefault
Definition: cdr_pgsql.c:98
const char * description
Definition: module.h:352
static char * pgpassword
Definition: cdr_pgsql.c:70
struct columns::@8 list
static char * table
Definition: cdr_pgsql.c:73
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
static PGconn * conn
Definition: cdr_pgsql.c:91
Asterisk main include file. File version handling, generic pbx functions.
#define ast_realloc(p, len)
A wrapper for realloc()
Definition: astmm.h:228
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:2988
#define ARRAY_LEN(a)
Definition: isdn_lib.c:42
#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:332
static const char config[]
Definition: cdr_pgsql.c:65
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static ast_mutex_t pgsql_lock
Definition: cdr_pgsql.c:89
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
descriptor for a cli entry.
Definition: cli.h:171
const int argc
Definition: cli.h:160
#define LOG_WARNING
Definition: logger.h:274
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
#define CONFIG_STATUS_FILEINVALID
#define PGSQL_MIN_VERSION_SCHEMA
Definition: cdr_pgsql.c:62
static int tmp()
Definition: bt_open.c:389
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
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_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:108
Definition: cli.h:152
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:1091
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
static struct ast_cli_entry cdr_pgsql_status_cli[]
Definition: cdr_pgsql.c:85
#define ast_mutex_lock(a)
Definition: lock.h:187
static char * pgappname
Definition: cdr_pgsql.c:71
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
#define NULL
Definition: resample.c:96
int value
Definition: syslog.c:37
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
#define LOG_DEBUG
Definition: logger.h:241
int args
This gets set in ast_cli_register()
Definition: cli.h:185
#define ast_strlen_zero(foo)
Definition: strings.h:52
static int totalrecords
Definition: cdr_pgsql.c:81
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:3050
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:1065
static char * pgdbname
Definition: cdr_pgsql.c:68
Configuration File Parser.
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:77
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
#define ast_config_load(filename, flags)
Load a config file.
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2943
General Asterisk PBX channel definitions.
const int fd
Definition: cli.h:159
#define AST_RWLIST_TRAVERSE
Definition: linkedlists.h:493
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: extconf.c:1290
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:193
#define CONFIG_STATUS_FILEUNCHANGED
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:290
struct timeval answer
Definition: cdr.h:296
Responsible for call detail data.
Definition: cdr.h:276
#define LOG_ERROR
Definition: logger.h:285
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
static int reload(void)
Definition: cdr_pgsql.c:798
#define CLI_SHOWUSAGE
Definition: cli.h:45
static char * tz
Definition: cdr_pgsql.c:75
#define LOG_NOTICE
Definition: logger.h:263
#define LENGTHEN_BUF1(size)
Definition: cdr_pgsql.c:119
static time_t connect_time
Definition: cdr_pgsql.c:80
static char version[AST_MAX_EXTENSION]
Definition: chan_ooh323.c:391
struct timeval start
Definition: cdr.h:294
static int maxsize
Definition: cdr_pgsql.c:79
#define ast_free(a)
Definition: astmm.h:182
char * command
Definition: cli.h:186
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:843
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
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
static char * pgdbport
Definition: cdr_pgsql.c:72
Structure used to handle boolean flags.
Definition: utils.h:199
#define AST_RWLIST_ENTRY
Definition: linkedlists.h:414
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
const char * usage
Definition: cli.h:177
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), s second(s)
Definition: main/cli.c:3021
static char * pghostname
Definition: cdr_pgsql.c:67
static const char name[]
Definition: cdr_pgsql.c:64
#define CLI_SUCCESS
Definition: cli.h:44
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:694
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:688
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:740
static int load_module(void)
Definition: cdr_pgsql.c:788
struct timeval end
Definition: cdr.h:298
unsigned int notnull
Definition: cdr_pgsql.c:97
Standard Command Line Interface.
static int pgsql_log(struct ast_cdr *cdr)
Definition: cdr_pgsql.c:214
static PGresult * result
Definition: cel_pgsql.c:88
static char * pgdbuser
Definition: cdr_pgsql.c:69
static int connected
Definition: cdr_pgsql.c:77
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:78
static int unload_module(void)
Definition: cdr_pgsql.c:497
static char * encoding
Definition: cdr_pgsql.c:74
static int config_module(int reload)
Definition: cdr_pgsql.c:524
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
#define DEBUG_ATLEAST(level)
Definition: logger.h:441
Asterisk module definitions.
static void pgsql_reconnect(void)
Definition: cdr_pgsql.c:174
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:125
#define DATE_FORMAT
Definition: cdr_pgsql.c:60
static void empty_columns(void)
Definition: cdr_pgsql.c:486
static int records
Definition: cdr_pgsql.c:82
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
jack_status_t status
Definition: app_jack.c:146
static int maxsize2
Definition: cdr_pgsql.c:79
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
#define ast_mutex_unlock(a)
Definition: lock.h:188
static struct test_val a
#define LENGTHEN_BUF2(size)
Definition: cdr_pgsql.c:121