Asterisk - The Open Source Telephony Project GIT-master-67613d1
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
60static const char name[] = "pgsql";
61static const char config[] = "cdr_pgsql.conf";
62
63static char *pghostname;
64static char *pgdbname;
65static char *pgdbuser;
66static char *pgpassword;
67static char *pgappname;
68static char *pgdbport;
69static char *table;
70static char *encoding;
71static char *tz;
72
73static int connected = 0;
74/* Optimization to reduce number of memory allocations */
75static int maxsize = 512, maxsize2 = 512;
76static time_t connect_time = 0;
77static int totalrecords = 0;
78static int records;
79
80static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
82 AST_CLI_DEFINE(handle_cdr_pgsql_status, "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)"),
83};
84
86
87static PGconn *conn = NULL;
88
89struct 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 */
121static 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
170static 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
184 ast_str_append(&conn_info, 0, "host=%s ", pghostname);
185 }
187 ast_str_append(&conn_info, 0, "port=%s ", pgdbport);
188 }
190 ast_str_append(&conn_info, 0, "dbname=%s ", pgdbname);
191 }
193 ast_str_append(&conn_info, 0, "user=%s ", pgdbuser);
194 }
196 ast_str_append(&conn_info, 0, "application_name=%s ", pgappname);
197 }
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
210static 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) {
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
471ast_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 */
482static void empty_columns(void)
483{
484 struct columns *current;
488 }
490
491}
492
493static int unload_module(void)
494{
496 return -1;
497 }
498
500
501 if (conn) {
502 PQfinish(conn);
503 conn = NULL;
504 }
513 ast_free(tz);
514
516
517 return 0;
518}
519
520static 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")) {
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))) {
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))) {
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))) {
579 return -1;
580 }
581
582 if (!(tmp = ast_variable_retrieve(cfg, "global", "appname"))) {
583 tmp = "";
584 }
585
587 if (!(pgappname = ast_strdup(tmp))) {
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))) {
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))) {
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
624 if (!(table = ast_strdup(tmp))) {
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))) {
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))) {
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
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);
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);
730 }
731
732 /* Clear out the columns list. */
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
779
781 return 0;
782}
783
784static int load_module(void)
785{
787 if (config_module(0)) {
789 }
792}
793
794static int reload(void)
795{
796 return config_module(1);
797}
798
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",
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:3112
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:3050
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:3005
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:3053
#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.
#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:783
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
@ CONFIG_FLAG_FILEUNCHANGED
#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
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_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
#define ast_mutex_unlock(a)
Definition: lock.h:190
#define ast_mutex_lock(a)
Definition: lock.h:189
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:520
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:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
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:659
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:1113
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
Responsible for call detail data.
Definition: cdr.h:279
struct timeval answer
Definition: cdr.h:299
struct timeval start
Definition: cdr.h:297
struct timeval end
Definition: cdr.h:301
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:623
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:87
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:117
#define ARRAY_LEN(a)
Definition: utils.h:666