Asterisk - The Open Source Telephony Project  21.4.1
cel_pgsql.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008
5  *
6  * Steve Murphy - adapted to CEL, from:
7  * Matthew D. Hardeman <mhardemn@papersoft.com>
8  * Adapted from the MySQL CDR logger originally by James Sharp
9  *
10  * Modified April, 2007; Dec, 2008
11  * Steve Murphy <murf@digium.com>
12 
13  * Modified September 2003
14  * Matthew D. Hardeman <mhardemn@papersoft.com>
15  *
16  * See http://www.asterisk.org for more information about
17  * the Asterisk project. Please do not directly contact
18  * any of the maintainers of this project for assistance;
19  * the project provides a web site, mailing lists and IRC
20  * channels for your use.
21  *
22  * This program is free software, distributed under the terms of
23  * the GNU General Public License Version 2. See the LICENSE file
24  * at the top of the source tree.
25  */
26 
27 /*! \file
28  *
29  * \brief PostgreSQL CEL logger
30  *
31  * \author Steve Murphy <murf@digium.com>
32  * PostgreSQL http://www.postgresql.org/
33  * \ingroup cel_drivers
34  */
35 
36 /*** MODULEINFO
37  <depend>pgsql</depend>
38  <support_level>extended</support_level>
39  ***/
40 
41 #include "asterisk.h"
42 
43 #include <libpq-fe.h>
44 
45 #include "asterisk/config.h"
46 #include "asterisk/options.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/cel.h"
49 #include "asterisk/module.h"
50 #include "asterisk/logger.h"
51 #include "asterisk.h"
52 
53 #define DATE_FORMAT "%Y-%m-%d %T.%6q"
54 
55 #define PGSQL_BACKEND_NAME "CEL PGSQL backend"
56 
57 #define PGSQL_MIN_VERSION_SCHEMA 70300
58 
59 static char *config = "cel_pgsql.conf";
60 
61 static char *pghostname;
62 static char *pgdbname;
63 static char *pgdbuser;
64 static char *pgpassword;
65 static char *pgappname;
66 static char *pgdbport;
67 static char *table;
68 static char *schema;
69 
70 static int connected = 0;
71 /* Optimization to reduce number of memory allocations */
72 static int maxsize = 512, maxsize2 = 512;
73 static int usegmtime = 0;
74 
75 /*! \brief show_user_def is off by default */
76 #define CEL_SHOW_USERDEF_DEFAULT 0
77 
78 /*! TRUE if we should set the eventtype field to USER_DEFINED on user events. */
79 static unsigned char cel_show_user_def;
80 
81 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
82 
83 static PGconn *conn = NULL;
84 static PGresult *result = NULL;
85 
86 struct columns {
87  char *name;
88  char *type;
89  int len;
90  unsigned int notnull:1;
91  unsigned int hasdefault:1;
92  AST_RWLIST_ENTRY(columns) list;
93 };
94 
96 
97 #define LENGTHEN_BUF(size, var_sql) \
98  do { \
99  /* Lengthen buffer, if necessary */ \
100  if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
101  if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 3) / 512 + 1) * 512) != 0) { \
102  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", pghostname, table); \
103  ast_free(sql); \
104  ast_free(sql2); \
105  AST_RWLIST_UNLOCK(&psql_columns); \
106  return; \
107  } \
108  } \
109  } while (0)
110 
111 #define LENGTHEN_BUF1(size) \
112  LENGTHEN_BUF(size, sql);
113 #define LENGTHEN_BUF2(size) \
114  LENGTHEN_BUF(size, sql2);
115 
116 static void pgsql_reconnect(void)
117 {
118  struct ast_str *conn_info = ast_str_create(128);
119  if (!conn_info) {
120  ast_log(LOG_ERROR, "Failed to allocate memory for connection string.\n");
121  return;
122  }
123 
124  if (conn) {
125  PQfinish(conn);
126  conn = NULL;
127  }
128 
129  ast_str_set(&conn_info, 0, "host=%s port=%s dbname=%s user=%s",
130  pghostname, pgdbport, pgdbname, pgdbuser);
131 
132  if (!ast_strlen_zero(pgappname)) {
133  ast_str_append(&conn_info, 0, " application_name=%s", pgappname);
134  }
135 
136  if (!ast_strlen_zero(pgpassword)) {
137  ast_str_append(&conn_info, 0, " password=%s", pgpassword);
138  }
139 
140  conn = PQconnectdb(ast_str_buffer(conn_info));
141  ast_free(conn_info);
142 }
143 
144 
145 static void pgsql_log(struct ast_event *event)
146 {
147  struct ast_tm tm;
148  char timestr[128];
149  char *pgerror;
150  struct ast_cel_event_record record = {
152  };
153 
154  if (ast_cel_fill_record(event, &record)) {
155  return;
156  }
157 
158  ast_mutex_lock(&pgsql_lock);
159 
160  ast_localtime(&record.event_time, &tm, usegmtime ? "GMT" : NULL);
161  ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
162 
163  if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
164  pgsql_reconnect();
165  if (PQstatus(conn) != CONNECTION_BAD) {
166  connected = 1;
167  } else {
168  pgerror = PQerrorMessage(conn);
169  ast_log(LOG_ERROR, "cel_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
170  ast_log(LOG_ERROR, "cel_pgsql: Reason: %s\n", pgerror);
171  PQfinish(conn);
172  conn = NULL;
173  }
174  }
175  if (connected) {
176  struct columns *cur;
177  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
178  char buf[257];
179  char *escapebuf = NULL;
180  const char *value;
181  int first = 1;
182  size_t bufsize = 513;
183 
184  escapebuf = ast_malloc(bufsize);
185  if (!escapebuf || !sql || !sql2) {
186  goto ast_log_cleanup;
187  }
188 
189  ast_str_set(&sql, 0, "INSERT INTO %s (", table);
190  ast_str_set(&sql2, 0, " VALUES (");
191 
192 #define SEP (first ? "" : ",")
193 
194  AST_RWLIST_RDLOCK(&psql_columns);
195  AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
196  LENGTHEN_BUF1(strlen(cur->name) + 2);
197  ast_str_append(&sql, 0, "%s\"%s\"", SEP, cur->name);
198 
199  if (strcmp(cur->name, "eventtime") == 0) {
200  if (strncmp(cur->type, "int", 3) == 0) {
201  LENGTHEN_BUF2(13);
202  ast_str_append(&sql2, 0, "%s%ld", SEP, (long) record.event_time.tv_sec);
203  } else if (strncmp(cur->type, "float", 5) == 0) {
204  LENGTHEN_BUF2(31);
205  ast_str_append(&sql2, 0, "%s%f",
206  SEP,
207  (double) record.event_time.tv_sec +
208  (double) record.event_time.tv_usec / 1000000.0);
209  } else {
210  /* char, hopefully */
211  LENGTHEN_BUF2(31);
212  ast_localtime(&record.event_time, &tm, usegmtime ? "GMT" : NULL);
213  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
214  ast_str_append(&sql2, 0, "%s'%s'", SEP, buf);
215  }
216  } else if (strcmp(cur->name, "eventtype") == 0) {
217  if (cur->type[0] == 'i') {
218  /* Get integer, no need to escape anything */
219  LENGTHEN_BUF2(5);
220  ast_str_append(&sql2, 0, "%s%d", SEP, (int) record.event_type);
221  } else if (strncmp(cur->type, "float", 5) == 0) {
222  LENGTHEN_BUF2(31);
223  ast_str_append(&sql2, 0, "%s%f", SEP, (double) record.event_type);
224  } else {
225  /* Char field, probably */
226  const char *event_name;
227 
228  event_name = (!cel_show_user_def
229  && record.event_type == AST_CEL_USER_DEFINED)
230  ? record.user_defined_name : record.event_name;
231  LENGTHEN_BUF2(strlen(event_name) + 1);
232  ast_str_append(&sql2, 0, "%s'%s'", SEP, event_name);
233  }
234  } else if (strcmp(cur->name, "amaflags") == 0) {
235  if (strncmp(cur->type, "int", 3) == 0) {
236  /* Integer, no need to escape anything */
237  LENGTHEN_BUF2(13);
238  ast_str_append(&sql2, 0, "%s%u", SEP, record.amaflag);
239  } else {
240  /* Although this is a char field, there are no special characters in the values for these fields */
241  LENGTHEN_BUF2(31);
242  ast_str_append(&sql2, 0, "%s'%u'", SEP, record.amaflag);
243  }
244  } else {
245  /* Arbitrary field, could be anything */
246  if (strcmp(cur->name, "userdeftype") == 0) {
247  value = record.user_defined_name;
248  } else if (strcmp(cur->name, "cid_name") == 0) {
249  value = record.caller_id_name;
250  } else if (strcmp(cur->name, "cid_num") == 0) {
251  value = record.caller_id_num;
252  } else if (strcmp(cur->name, "cid_ani") == 0) {
253  value = record.caller_id_ani;
254  } else if (strcmp(cur->name, "cid_rdnis") == 0) {
255  value = record.caller_id_rdnis;
256  } else if (strcmp(cur->name, "cid_dnid") == 0) {
257  value = record.caller_id_dnid;
258  } else if (strcmp(cur->name, "exten") == 0) {
259  value = record.extension;
260  } else if (strcmp(cur->name, "context") == 0) {
261  value = record.context;
262  } else if (strcmp(cur->name, "channame") == 0) {
263  value = record.channel_name;
264  } else if (strcmp(cur->name, "appname") == 0) {
265  value = record.application_name;
266  } else if (strcmp(cur->name, "appdata") == 0) {
267  value = record.application_data;
268  } else if (strcmp(cur->name, "accountcode") == 0) {
269  value = record.account_code;
270  } else if (strcmp(cur->name, "peeraccount") == 0) {
271  value = record.peer_account;
272  } else if (strcmp(cur->name, "uniqueid") == 0) {
273  value = record.unique_id;
274  } else if (strcmp(cur->name, "linkedid") == 0) {
275  value = record.linked_id;
276  } else if (strcmp(cur->name, "userfield") == 0) {
277  value = record.user_field;
278  } else if (strcmp(cur->name, "peer") == 0) {
279  value = record.peer;
280  } else if (strcmp(cur->name, "extra") == 0) {
281  value = record.extra;
282  } else {
283  value = NULL;
284  }
285 
286  if (value == NULL) {
287  ast_str_append(&sql2, 0, "%sDEFAULT", SEP);
288  } else if (strncmp(cur->type, "int", 3) == 0) {
289  long long whatever;
290  if (value && sscanf(value, "%30lld", &whatever) == 1) {
291  LENGTHEN_BUF2(26);
292  ast_str_append(&sql2, 0, "%s%lld", SEP, whatever);
293  } else {
294  LENGTHEN_BUF2(2);
295  ast_str_append(&sql2, 0, "%s0", SEP);
296  }
297  } else if (strncmp(cur->type, "float", 5) == 0) {
298  long double whatever;
299  if (value && sscanf(value, "%30Lf", &whatever) == 1) {
300  LENGTHEN_BUF2(51);
301  ast_str_append(&sql2, 0, "%s%30Lf", SEP, whatever);
302  } else {
303  LENGTHEN_BUF2(2);
304  ast_str_append(&sql2, 0, "%s0", SEP);
305  }
306  /* XXX Might want to handle dates, times, and other misc fields here XXX */
307  } else {
308  if (value) {
309  size_t required_size = strlen(value) * 2 + 1;
310 
311  /* If our argument size exceeds our buffer, grow it,
312  * as PQescapeStringConn() expects the buffer to be
313  * adequitely sized and does *NOT* do size checking.
314  */
315  if (required_size > bufsize) {
316  char *tmpbuf = ast_realloc(escapebuf, required_size);
317 
318  if (!tmpbuf) {
319  AST_RWLIST_UNLOCK(&psql_columns);
320  goto ast_log_cleanup;
321  }
322 
323  escapebuf = tmpbuf;
324  bufsize = required_size;
325  }
326  PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
327  } else {
328  escapebuf[0] = '\0';
329  }
330  LENGTHEN_BUF2(strlen(escapebuf) + 3);
331  ast_str_append(&sql2, 0, "%s'%s'", SEP, escapebuf);
332  }
333  }
334  first = 0;
335  }
336  AST_RWLIST_UNLOCK(&psql_columns);
337  LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
338  ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
339 
340  ast_debug(3, "Inserting a CEL record: [%s].\n", ast_str_buffer(sql));
341  /* Test to be sure we're still connected... */
342  /* If we're connected, and connection is working, good. */
343  /* Otherwise, attempt reconnect. If it fails... sorry... */
344  if (PQstatus(conn) == CONNECTION_OK) {
345  connected = 1;
346  } else {
347  ast_log(LOG_WARNING, "Connection was lost... attempting to reconnect.\n");
348  PQreset(conn);
349  if (PQstatus(conn) == CONNECTION_OK) {
350  ast_log(LOG_NOTICE, "Connection reestablished.\n");
351  connected = 1;
352  } else {
353  pgerror = PQerrorMessage(conn);
354  ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
355  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
356  PQfinish(conn);
357  conn = NULL;
358  connected = 0;
359  goto ast_log_cleanup;
360  }
361  }
362  result = PQexec(conn, ast_str_buffer(sql));
363  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
364  pgerror = PQresultErrorMessage(result);
365  ast_log(LOG_WARNING, "Failed to insert call detail record into database!\n");
366  ast_log(LOG_WARNING, "Reason: %s\n", pgerror);
367  ast_log(LOG_WARNING, "Connection may have been lost... attempting to reconnect.\n");
368  PQreset(conn);
369  if (PQstatus(conn) == CONNECTION_OK) {
370  ast_log(LOG_NOTICE, "Connection reestablished.\n");
371  connected = 1;
372  PQclear(result);
373  result = PQexec(conn, ast_str_buffer(sql));
374  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
375  pgerror = PQresultErrorMessage(result);
376  ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
377  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
378  }
379  }
380  }
381  PQclear(result);
382 
383  /* Next time, just allocate buffers that are that big to start with. */
384  if (ast_str_strlen(sql) > maxsize) {
385  maxsize = ast_str_strlen(sql);
386  }
387  if (ast_str_strlen(sql2) > maxsize2) {
388  maxsize2 = ast_str_strlen(sql2);
389  }
390 
391 ast_log_cleanup:
392  ast_free(sql);
393  ast_free(sql2);
394  ast_free(escapebuf);
395  }
396 
397  ast_mutex_unlock(&pgsql_lock);
398 }
399 
400 static int my_unload_module(void)
401 {
402  struct columns *current;
403 
404  ast_cel_backend_unregister(PGSQL_BACKEND_NAME);
405  AST_RWLIST_WRLOCK(&psql_columns);
406  if (conn) {
407  PQfinish(conn);
408  conn = NULL;
409  }
410  if (pghostname) {
411  ast_free(pghostname);
412  pghostname = NULL;
413  }
414  if (pgdbname) {
415  ast_free(pgdbname);
416  pgdbname = NULL;
417  }
418  if (pgdbuser) {
419  ast_free(pgdbuser);
420  pgdbuser = NULL;
421  }
422  if (pgpassword) {
423  ast_free(pgpassword);
424  pgpassword = NULL;
425  }
426  if (pgappname) {
427  ast_free(pgappname);
428  pgappname = NULL;
429  }
430  if (pgdbport) {
431  ast_free(pgdbport);
432  pgdbport = NULL;
433  }
434  if (table) {
435  ast_free(table);
436  table = NULL;
437  }
438  if (schema) {
439  ast_free(schema);
440  schema = NULL;
441  }
442  while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
443  ast_free(current);
444  }
445  AST_RWLIST_UNLOCK(&psql_columns);
446  return 0;
447 }
448 
449 static int unload_module(void)
450 {
451  return my_unload_module();
452 }
453 
454 static int process_my_load_module(struct ast_config *cfg)
455 {
456  struct ast_variable *var;
457  char *pgerror;
458  const char *tmp;
459  PGresult *result;
460  struct columns *cur;
461 
462  if (!(var = ast_variable_browse(cfg, "global"))) {
463  ast_log(LOG_WARNING,"CEL pgsql config file missing global section.\n");
465  }
466  if (!(tmp = ast_variable_retrieve(cfg,"global","hostname"))) {
467  ast_log(LOG_WARNING,"PostgreSQL server hostname not specified. Assuming unix socket connection\n");
468  tmp = ""; /* connect via UNIX-socket by default */
469  }
470  if (pghostname)
471  ast_free(pghostname);
472  if (!(pghostname = ast_strdup(tmp))) {
473  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying host info\n");
475  }
476  if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
477  ast_log(LOG_WARNING,"PostgreSQL database not specified. Assuming asterisk\n");
478  tmp = "asteriskceldb";
479  }
480  if (pgdbname)
481  ast_free(pgdbname);
482  if (!(pgdbname = ast_strdup(tmp))) {
483  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying dbname info\n");
485  }
486  if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
487  ast_log(LOG_WARNING,"PostgreSQL database user not specified. Assuming asterisk\n");
488  tmp = "asterisk";
489  }
490  if (pgdbuser)
491  ast_free(pgdbuser);
492  if (!(pgdbuser = ast_strdup(tmp))) {
493  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying user info\n");
495  }
496  if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
497  ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
498  tmp = "";
499  }
500  if (pgpassword)
501  ast_free(pgpassword);
502  if (!(pgpassword = ast_strdup(tmp))) {
503  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying password info\n");
505  }
506  if (!(tmp = ast_variable_retrieve(cfg, "global", "appname"))) {
507  tmp = "";
508  }
509  if (pgappname) {
510  ast_free(pgappname);
511  }
512  if (!(pgappname = ast_strdup(tmp))) {
513  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying appname info\n");
515  }
516 
517  if (!(tmp = ast_variable_retrieve(cfg,"global","port"))) {
518  ast_log(LOG_WARNING,"PostgreSQL database port not specified. Using default 5432.\n");
519  tmp = "5432";
520  }
521  if (pgdbport)
522  ast_free(pgdbport);
523  if (!(pgdbport = ast_strdup(tmp))) {
524  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying port info\n");
526  }
527  if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
528  ast_log(LOG_WARNING,"CEL table not specified. Assuming cel\n");
529  tmp = "cel";
530  }
531  if (table)
532  ast_free(table);
533  if (!(table = ast_strdup(tmp))) {
535  }
537  if ((tmp = ast_variable_retrieve(cfg, "global", "show_user_defined"))) {
538  cel_show_user_def = ast_true(tmp) ? 1 : 0;
539  }
540  if ((tmp = ast_variable_retrieve(cfg, "global", "usegmtime"))) {
541  usegmtime = ast_true(tmp);
542  } else {
543  usegmtime = 0;
544  }
545  if (!(tmp = ast_variable_retrieve(cfg, "global", "schema"))) {
546  tmp = "";
547  }
548  if (schema) {
549  ast_free(schema);
550  }
551  if (!(schema = ast_strdup(tmp))) {
552  ast_log(LOG_WARNING,"PostgreSQL Ran out of memory copying schema info\n");
554  }
555  if (DEBUG_ATLEAST(3)) {
556  if (ast_strlen_zero(pghostname)) {
557  ast_log(LOG_DEBUG, "cel_pgsql: using default unix socket\n");
558  } else {
559  ast_log(LOG_DEBUG, "cel_pgsql: got hostname of %s\n", pghostname);
560  }
561  ast_log(LOG_DEBUG, "cel_pgsql: got port of %s\n", pgdbport);
562  ast_log(LOG_DEBUG, "cel_pgsql: got user of %s\n", pgdbuser);
563  ast_log(LOG_DEBUG, "cel_pgsql: got dbname of %s\n", pgdbname);
564  ast_log(LOG_DEBUG, "cel_pgsql: got password of %s\n", pgpassword);
565  ast_log(LOG_DEBUG, "cel_pgsql: got sql table name of %s\n", table);
566  ast_log(LOG_DEBUG, "cel_pgsql: got show_user_defined of %s\n",
567  cel_show_user_def ? "Yes" : "No");
568  }
569 
570  pgsql_reconnect();
571  if (PQstatus(conn) != CONNECTION_BAD) {
572  char sqlcmd[768];
573  char *fname, *ftype, *flen, *fnotnull, *fdef, *tablename, *tmp_tablename;
574  int i, rows, version;
575 
576  ast_debug(1, "Successfully connected to PostgreSQL database.\n");
577  connected = 1;
578 
579  version = PQserverVersion(conn);
580  /* Remove any schema name from the table */
581  if ((tmp_tablename = strrchr(table, '.'))) {
582  tmp_tablename++;
583  } else {
584  tmp_tablename = table;
585  }
586  tablename = ast_alloca(strlen(tmp_tablename) * 2 + 1);
587  PQescapeStringConn(conn, tablename, tmp_tablename, strlen(tmp_tablename), NULL);
588  if (version >= PGSQL_MIN_VERSION_SCHEMA) {
589  char *schemaname;
590  int lenschema;
591  lenschema = strlen(schema);
592  schemaname = ast_alloca(lenschema * 2 + 1);
593  PQescapeStringConn(conn, schemaname, schema, lenschema, NULL);
594 
595  snprintf(sqlcmd, sizeof(sqlcmd),
596  "SELECT a.attname, t.typname, a.attlen, a.attnotnull, pg_catalog.pg_get_expr(d.adbin, d.adrelid) adsrc, a.atttypmod "
597  "FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
598  "AND c.relname = '%s' AND n.nspname = %s%s%s) "
599  "INNER JOIN pg_catalog.pg_attribute a ON ("
600  "NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) "
601  "INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) "
602  "LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid "
603  "AND d.adnum = a.attnum "
604  "ORDER BY n.nspname, c.relname, attnum",
605  tablename,
606  lenschema == 0 ? "" : "'", lenschema == 0 ? "current_schema()" : schemaname, lenschema == 0 ? "" : "'");
607  } else {
608  snprintf(sqlcmd, sizeof(sqlcmd),
609  "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod "
610  "FROM pg_class c, pg_type t, pg_attribute a "
611  "LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid "
612  "AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid "
613  "AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", tablename);
614  }
615  /* Query the columns */
616  result = PQexec(conn, sqlcmd);
617  if (PQresultStatus(result) != PGRES_TUPLES_OK) {
618  pgerror = PQresultErrorMessage(result);
619  ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
620  PQclear(result);
621  unload_module();
623  }
624 
625  rows = PQntuples(result);
626  for (i = 0; i < rows; i++) {
627  fname = PQgetvalue(result, i, 0);
628  ftype = PQgetvalue(result, i, 1);
629  flen = PQgetvalue(result, i, 2);
630  fnotnull = PQgetvalue(result, i, 3);
631  fdef = PQgetvalue(result, i, 4);
632  ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
633  cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
634  if (cur) {
635  sscanf(flen, "%30d", &cur->len);
636  cur->name = (char *)cur + sizeof(*cur);
637  cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
638  strcpy(cur->name, fname);
639  strcpy(cur->type, ftype);
640  if (*fnotnull == 't') {
641  cur->notnull = 1;
642  } else {
643  cur->notnull = 0;
644  }
645  if (!ast_strlen_zero(fdef)) {
646  cur->hasdefault = 1;
647  } else {
648  cur->hasdefault = 0;
649  }
650  AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
651  }
652  }
653  PQclear(result);
654  } else {
655  pgerror = PQerrorMessage(conn);
656  ast_log(LOG_ERROR, "cel_pgsql: Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
657  ast_log(LOG_ERROR, "cel_pgsql: Reason: %s\n", pgerror);
658  connected = 0;
659  PQfinish(conn);
660  conn = NULL;
661  }
663 }
664 
665 static int my_load_module(int reload)
666 {
667  struct ast_config *cfg;
668  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
669 
670  if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
671  ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CEL's: %s\n", config);
673  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
675  }
676 
677  if (reload) {
678  my_unload_module();
679  }
680 
681  process_my_load_module(cfg);
682  ast_config_destroy(cfg);
683 
684  if (ast_cel_backend_register(PGSQL_BACKEND_NAME, pgsql_log)) {
685  ast_log(LOG_WARNING, "Unable to subscribe to CEL events for pgsql\n");
687  }
688 
690 }
691 
692 static int load_module(void)
693 {
694  return my_load_module(0);
695 }
696 
697 static int reload(void)
698 {
699  return my_load_module(1);
700 }
701 
702 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CEL Backend",
703  .support_level = AST_MODULE_SUPPORT_EXTENDED,
704  .load = load_module,
705  .unload = unload_module,
706  .reload = reload,
707  .load_pri = AST_MODPRI_CDR_DRIVER,
708  .requires = "cel",
709 );
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:138
An event.
Definition: event.c:81
Asterisk main include file. File version handling, generic pbx functions.
#define ast_realloc(p, len)
A wrapper for realloc()
Definition: astmm.h:226
Call Event Logging API.
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1781
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
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
Structure for variables, used for configurations and for channel variables.
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
Definition: astman.c:222
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
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#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
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
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
General Asterisk PBX channel definitions.
uint32_t version
struct ABI version
Definition: cel.h:148
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_debug(level,...)
Log a DEBUG message.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1769
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: utils.c:2199
Support for dynamic strings.
Definition: strings.h:623
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Support for logging to various files, console and syslog Configuration in file logger.conf.
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
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:143
Structure used to handle boolean flags.
Definition: utils.h:199
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
static unsigned char cel_show_user_def
Definition: cel_pgsql.c:79
Options provided by main asterisk program.
a user-defined event, the event name field should be set
Definition: cel.h:69
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
#define CEL_SHOW_USERDEF_DEFAULT
show_user_def is off by default
Definition: cel_pgsql.c:76
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
int ast_cel_fill_record(const struct ast_event *event, struct ast_cel_event_record *r)
Fill in an ast_cel_event_record from a CEL event.
Definition: cel.c:821
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659