Asterisk - The Open Source Telephony Project  21.4.1
res_config_pgsql.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2017, Digium, Inc.
5  *
6  * Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
7  * Mark Spencer <markster@digium.com> - Asterisk Author
8  * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
9  *
10  * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine>
11  *
12  * v1.0 - (07-11-05) - Initial version based on res_config_mysql v2.0
13  */
14 
15 /*! \file
16  *
17  * \brief PostgreSQL plugin for Asterisk RealTime Architecture
18  *
19  * \author Mark Spencer <markster@digium.com>
20  * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
21  *
22  * PostgreSQL http://www.postgresql.org
23  */
24 
25 /*** MODULEINFO
26  <depend>pgsql</depend>
27  <support_level>extended</support_level>
28  ***/
29 
30 #include "asterisk.h"
31 
32 #include <libpq-fe.h> /* PostgreSQL */
33 
34 #include "asterisk/file.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/config.h"
38 #include "asterisk/module.h"
39 #include "asterisk/lock.h"
40 #include "asterisk/utils.h"
41 #include "asterisk/cli.h"
42 
43 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
44 AST_THREADSTORAGE(sql_buf);
45 AST_THREADSTORAGE(findtable_buf);
46 AST_THREADSTORAGE(where_buf);
47 AST_THREADSTORAGE(escapebuf_buf);
48 AST_THREADSTORAGE(semibuf_buf);
49 
50 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
51 
52 static PGconn *pgsqlConn = NULL;
53 static int version;
54 #define has_schema_support (version > 70300 ? 1 : 0)
55 #define USE_BACKSLASH_AS_STRING (version >= 90100 ? 1 : 0)
56 
57 #define MAX_DB_OPTION_SIZE 64
58 
59 struct columns {
60  char *name;
61  char *type;
62  int len;
63  unsigned int notnull:1;
64  unsigned int hasdefault:1;
65  AST_LIST_ENTRY(columns) list;
66 };
67 
68 struct tables {
71  AST_LIST_ENTRY(tables) list;
72  char name[0];
73 };
74 
76 
77 static char dbhost[MAX_DB_OPTION_SIZE] = "";
78 static char dbuser[MAX_DB_OPTION_SIZE] = "";
79 static char dbpass[MAX_DB_OPTION_SIZE] = "";
80 static char dbname[MAX_DB_OPTION_SIZE] = "";
81 static char dbappname[MAX_DB_OPTION_SIZE] = "";
82 static char dbsock[MAX_DB_OPTION_SIZE] = "";
83 static int dbport = 5432;
84 static time_t connect_time = 0;
85 static int order_multi_row_results_by_initial_column = 1;
86 
87 static int parse_config(int reload);
88 static int pgsql_reconnect(const char *database);
89 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
90 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
91 
92 static enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
93 
94 static struct ast_cli_entry cli_realtime[] = {
95  AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
96  AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
97 };
98 
99 #define ESCAPE_STRING(buffer, stringname) \
100  do { \
101  int len = strlen(stringname); \
102  struct ast_str *semi = ast_str_thread_get(&semibuf_buf, len * 3 + 1); \
103  const char *chunk = stringname; \
104  ast_str_reset(semi); \
105  for (; *chunk; chunk++) { \
106  if (strchr(";^", *chunk)) { \
107  ast_str_append(&semi, 0, "^%02hhX", *chunk); \
108  } else { \
109  ast_str_append(&semi, 0, "%c", *chunk); \
110  } \
111  } \
112  if (ast_str_strlen(semi) > (ast_str_size(buffer) - 1) / 2) { \
113  ast_str_make_space(&buffer, ast_str_strlen(semi) * 2 + 1); \
114  } \
115  PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), ast_str_buffer(semi), ast_str_size(buffer), &pgresult); \
116  } while (0)
117 
118 static void destroy_table(struct tables *table)
119 {
120  struct columns *column;
121  ast_rwlock_wrlock(&table->lock);
122  while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
123  ast_free(column);
124  }
125  ast_rwlock_unlock(&table->lock);
126  ast_rwlock_destroy(&table->lock);
127  ast_free(table);
128 }
129 
130 /*! \brief Helper function for pgsql_exec. For running queries, use pgsql_exec()
131  *
132  * Connect if not currently connected. Run the given query.
133  *
134  * \param database database name we are connected to (used for error logging)
135  * \param tablename table name we are connected to (used for error logging)
136  * \param sql sql query string to execute
137  * \param result pointer for where to store the result handle
138  *
139  * \return -1 on fatal query error
140  * \return -2 on query failure that resulted in disconnection
141  * \return 0 on success
142  *
143  * \note see pgsql_exec for full example
144  */
145 static int _pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
146 {
147  ExecStatusType result_status;
148 
149  if (!pgsqlConn) {
150  ast_debug(1, "PostgreSQL connection not defined, connecting\n");
151 
152  if (pgsql_reconnect(database) != 1) {
153  ast_log(LOG_NOTICE, "reconnect failed\n");
154  *result = NULL;
155  return -1;
156  }
157 
158  ast_debug(1, "PostgreSQL connection successful\n");
159  }
160 
161  *result = PQexec(pgsqlConn, sql);
162  result_status = PQresultStatus(*result);
163  if (result_status != PGRES_COMMAND_OK
164  && result_status != PGRES_TUPLES_OK
165  && result_status != PGRES_NONFATAL_ERROR) {
166 
167  ast_log(LOG_ERROR, "PostgreSQL RealTime: Failed to query '%s@%s'.\n", tablename, database);
168  ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed: %s\n", sql);
169  ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
170  PQresultErrorMessage(*result),
171  PQresStatus(result_status));
172 
173  /* we may have tried to run a command on a disconnected/disconnecting handle */
174  /* are we no longer connected to the database... if not try again */
175  if (PQstatus(pgsqlConn) != CONNECTION_OK) {
176  PQfinish(pgsqlConn);
177  pgsqlConn = NULL;
178  return -2;
179  }
180 
181  /* connection still okay, which means the query is just plain bad */
182  return -1;
183  }
184 
185  ast_debug(1, "PostgreSQL query successful: %s\n", sql);
186  return 0;
187 }
188 
189 /*! \brief Do a postgres query, with reconnection support
190  *
191  * Connect if not currently connected. Run the given query
192  * and if we're disconnected afterwards, reconnect and query again.
193  *
194  * \param database database name we are connected to (used for error logging)
195  * \param tablename table name we are connected to (used for error logging)
196  * \param sql sql query string to execute
197  * \param result pointer for where to store the result handle
198  *
199  * \return -1 on query failure
200  * \return 0 on success
201  *
202  * \code
203  * int i, rows;
204  * PGresult *result;
205  * char *field_name, *field_type, *field_len, *field_notnull, *field_default;
206  *
207  * pgsql_exec("db", "table", "SELECT 1", &result)
208  *
209  * rows = PQntuples(result);
210  * for (i = 0; i < rows; i++) {
211  * field_name = PQgetvalue(result, i, 0);
212  * field_type = PQgetvalue(result, i, 1);
213  * field_len = PQgetvalue(result, i, 2);
214  * field_notnull = PQgetvalue(result, i, 3);
215  * field_default = PQgetvalue(result, i, 4);
216  * }
217  * \endcode
218  */
219 static int pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
220 {
221  int attempts = 0;
222  int res;
223 
224  /* Try the query, note failure if any */
225  /* On first failure, reconnect and try again (_pgsql_exec handles reconnect) */
226  /* On second failure, treat as fatal query error */
227 
228  while (attempts++ < 2) {
229  ast_debug(1, "PostgreSQL query attempt %d\n", attempts);
230  res = _pgsql_exec(database, tablename, sql, result);
231 
232  if (res == 0) {
233  if (attempts > 1) {
234  ast_log(LOG_NOTICE, "PostgreSQL RealTime: Query finally succeeded: %s\n", sql);
235  }
236 
237  return 0;
238  }
239 
240  if (res == -1) {
241  return -1; /* Still connected to db, but could not process query (fatal error) */
242  }
243 
244  /* res == -2 (query on a disconnected handle) */
245  ast_debug(1, "PostgreSQL query attempt %d failed, trying again\n", attempts);
246  }
247 
248  return -1;
249 }
250 
251 static struct tables *find_table(const char *database, const char *orig_tablename)
252 {
253  struct columns *column;
254  struct tables *table;
255  struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
256  RAII_VAR(PGresult *, result, NULL, PQclear);
257  int exec_result;
258  char *fname, *ftype, *flen, *fnotnull, *fdef;
259  int i, rows;
260 
262  AST_LIST_TRAVERSE(&psql_tables, table, list) {
263  if (!strcasecmp(table->name, orig_tablename)) {
264  ast_debug(1, "Found table in cache; now locking\n");
265  ast_rwlock_rdlock(&table->lock);
266  ast_debug(1, "Lock cached table; now returning\n");
268  return table;
269  }
270  }
271 
272  if (database == NULL) {
274  return NULL;
275  }
276 
277  ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
278 
279  /* Not found, scan the table */
280  if (has_schema_support) {
281  char *schemaname, *tablename, *tmp_schemaname, *tmp_tablename;
282  if (strchr(orig_tablename, '.')) {
283  tmp_schemaname = ast_strdupa(orig_tablename);
284  tmp_tablename = strchr(tmp_schemaname, '.');
285  *tmp_tablename++ = '\0';
286  } else {
287  tmp_schemaname = "";
288  tmp_tablename = ast_strdupa(orig_tablename);
289  }
290 
291  tablename = ast_alloca(strlen(tmp_tablename) * 2 + 1);
292  PQescapeStringConn(pgsqlConn, tablename, tmp_tablename, strlen(tmp_tablename), NULL);
293  schemaname = ast_alloca(strlen(tmp_schemaname) * 2 + 1);
294  PQescapeStringConn(pgsqlConn, schemaname, tmp_schemaname, strlen(tmp_schemaname), NULL);
295 
296  ast_str_set(&sql, 0, "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",
297  tablename,
298  ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
299  } else {
300  char *tablename;
301  tablename = ast_alloca(strlen(orig_tablename) * 2 + 1);
302  PQescapeStringConn(pgsqlConn, tablename, orig_tablename, strlen(orig_tablename), NULL);
303 
304  ast_str_set(&sql, 0, "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", tablename);
305  }
306 
307  ast_mutex_lock(&pgsql_lock);
308  exec_result = pgsql_exec(database, orig_tablename, ast_str_buffer(sql), &result);
309  ast_mutex_unlock(&pgsql_lock);
310  ast_debug(1, "Query of table structure complete. Now retrieving results.\n");
311  if (exec_result != 0) {
312  ast_log(LOG_ERROR, "Failed to query database columns for table %s\n", orig_tablename);
314  return NULL;
315  }
316 
317  if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_tablename) + 1))) {
318  ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
320  return NULL;
321  }
322  strcpy(table->name, orig_tablename); /* SAFE */
323  ast_rwlock_init(&table->lock);
324  AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
325 
326  rows = PQntuples(result);
327  for (i = 0; i < rows; i++) {
328  fname = PQgetvalue(result, i, 0);
329  ftype = PQgetvalue(result, i, 1);
330  flen = PQgetvalue(result, i, 2);
331  fnotnull = PQgetvalue(result, i, 3);
332  fdef = PQgetvalue(result, i, 4);
333  ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
334 
335  if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
336  ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", orig_tablename, fname);
337  destroy_table(table);
339  return NULL;
340  }
341 
342  if (strcmp(flen, "-1") == 0) {
343  /* Some types, like chars, have the length stored in a different field */
344  flen = PQgetvalue(result, i, 5);
345  sscanf(flen, "%30d", &column->len);
346  column->len -= 4;
347  } else {
348  sscanf(flen, "%30d", &column->len);
349  }
350  column->name = (char *)column + sizeof(*column);
351  column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
352  strcpy(column->name, fname);
353  strcpy(column->type, ftype);
354  if (*fnotnull == 't') {
355  column->notnull = 1;
356  } else {
357  column->notnull = 0;
358  }
359  if (!ast_strlen_zero(fdef)) {
360  column->hasdefault = 1;
361  } else {
362  column->hasdefault = 0;
363  }
364  AST_LIST_INSERT_TAIL(&table->columns, column, list);
365  }
366 
367  AST_LIST_INSERT_TAIL(&psql_tables, table, list);
368  ast_rwlock_rdlock(&table->lock);
370  return table;
371 }
372 
373 #define release_table(table) ast_rwlock_unlock(&(table)->lock);
374 
375 static struct columns *find_column(struct tables *t, const char *colname)
376 {
377  struct columns *column;
378 
379  /* Check that the column exists in the table */
380  AST_LIST_TRAVERSE(&t->columns, column, list) {
381  if (strcmp(column->name, colname) == 0) {
382  return column;
383  }
384  }
385  return NULL;
386 }
387 
388 #define IS_SQL_LIKE_CLAUSE(x) ((x) && ast_ends_with(x, " LIKE"))
389 #define ESCAPE_CLAUSE (USE_BACKSLASH_AS_STRING ? " ESCAPE '\\'" : " ESCAPE '\\\\'")
390 
391 static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, const struct ast_variable *fields)
392 {
393  RAII_VAR(PGresult *, result, NULL, PQclear);
394  int pgresult;
395  struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
396  struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
397  char *stringp;
398  char *chunk;
399  char *op;
400  char *escape = "";
401  const struct ast_variable *field = fields;
402  struct ast_variable *var = NULL, *prev = NULL;
403 
404  /*
405  * Ignore database from the extconfig.conf since it was
406  * configured by res_pgsql.conf.
407  */
408  database = dbname;
409 
410  if (!tablename) {
411  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
412  return NULL;
413  }
414 
415  /*
416  * Must connect to the server before anything else as ESCAPE_STRING()
417  * uses pgsqlConn
418  */
419  ast_mutex_lock(&pgsql_lock);
420  if (!pgsql_reconnect(database)) {
421  ast_mutex_unlock(&pgsql_lock);
422  return NULL;
423  }
424 
425  /* Get the first parameter and first value in our list of passed paramater/value pairs */
426  if (!field) {
427  ast_log(LOG_WARNING,
428  "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
429  if (pgsqlConn) {
430  PQfinish(pgsqlConn);
431  pgsqlConn = NULL;
432  }
433  ast_mutex_unlock(&pgsql_lock);
434  return NULL;
435  }
436 
437  /* Create the first part of the query using the first parameter/value pairs we just extracted
438  If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
439  if (!strchr(field->name, ' ')) {
440  op = " =";
441  } else {
442  op = "";
443  if (IS_SQL_LIKE_CLAUSE(field->name)) {
444  escape = ESCAPE_CLAUSE;
445  }
446  }
447 
448  ESCAPE_STRING(escapebuf, field->value);
449  if (pgresult) {
450  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
451  ast_mutex_unlock(&pgsql_lock);
452  return NULL;
453  }
454 
455  ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", tablename, field->name, op, ast_str_buffer(escapebuf), escape);
456  while ((field = field->next)) {
457  escape = "";
458  if (!strchr(field->name, ' ')) {
459  op = " =";
460  } else {
461  op = "";
462  if (IS_SQL_LIKE_CLAUSE(field->name)) {
463  escape = ESCAPE_CLAUSE;
464  }
465  }
466 
467  ESCAPE_STRING(escapebuf, field->value);
468  if (pgresult) {
469  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
470  ast_mutex_unlock(&pgsql_lock);
471  return NULL;
472  }
473 
474  ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(escapebuf), escape);
475  }
476  ast_str_append(&sql, 0, " LIMIT 1");
477 
478  /* We now have our complete statement; Lets connect to the server and execute it. */
479  if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
480  ast_mutex_unlock(&pgsql_lock);
481  return NULL;
482  }
483 
484  ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
485 
486  if (PQntuples(result) > 0) {
487  int i = 0;
488  int numFields = PQnfields(result);
489  char **fieldnames = NULL;
490 
491  ast_debug(1, "PostgreSQL RealTime: Found a row.\n");
492 
493  if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
494  ast_mutex_unlock(&pgsql_lock);
495  return NULL;
496  }
497  for (i = 0; i < numFields; i++)
498  fieldnames[i] = PQfname(result, i);
499  for (i = 0; i < numFields; i++) {
500  stringp = PQgetvalue(result, 0, i);
501  while (stringp) {
502  chunk = strsep(&stringp, ";");
503  if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
504  if (prev) {
505  prev->next = ast_variable_new(fieldnames[i], chunk, "");
506  if (prev->next) {
507  prev = prev->next;
508  }
509  } else {
510  prev = var = ast_variable_new(fieldnames[i], chunk, "");
511  }
512  }
513  }
514  }
515  ast_free(fieldnames);
516  } else {
517  ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
518  }
519 
520  ast_mutex_unlock(&pgsql_lock);
521 
522  return var;
523 }
524 
525 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, const struct ast_variable *fields)
526 {
527  RAII_VAR(PGresult *, result, NULL, PQclear);
528  int num_rows = 0, pgresult;
529  struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
530  struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
531  const struct ast_variable *field = fields;
532  const char *initfield = NULL;
533  char *stringp;
534  char *chunk;
535  char *op;
536  char *escape = "";
537  struct ast_variable *var = NULL;
538  struct ast_config *cfg = NULL;
539  struct ast_category *cat = NULL;
540 
541  /*
542  * Ignore database from the extconfig.conf since it was
543  * configured by res_pgsql.conf.
544  */
545  database = dbname;
546 
547  if (!table) {
548  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
549  return NULL;
550  }
551 
552  if (!(cfg = ast_config_new()))
553  return NULL;
554 
555  /*
556  * Must connect to the server before anything else as ESCAPE_STRING()
557  * uses pgsqlConn
558  */
559  ast_mutex_lock(&pgsql_lock);
560  if (!pgsql_reconnect(database)) {
561  ast_mutex_unlock(&pgsql_lock);
562  return NULL;
563  }
564 
565  /* Get the first parameter and first value in our list of passed paramater/value pairs */
566  if (!field) {
567  ast_log(LOG_WARNING,
568  "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
569  if (pgsqlConn) {
570  PQfinish(pgsqlConn);
571  pgsqlConn = NULL;
572  }
573  ast_mutex_unlock(&pgsql_lock);
574  ast_config_destroy(cfg);
575  return NULL;
576  }
577 
578  initfield = ast_strdupa(field->name);
579  if ((op = strchr(initfield, ' '))) {
580  *op = '\0';
581  }
582 
583  /* Create the first part of the query using the first parameter/value pairs we just extracted
584  If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
585 
586  if (!strchr(field->name, ' ')) {
587  op = " =";
588  escape = "";
589  } else {
590  op = "";
591  if (IS_SQL_LIKE_CLAUSE(field->name)) {
592  escape = ESCAPE_CLAUSE;
593  }
594  }
595 
596  ESCAPE_STRING(escapebuf, field->value);
597  if (pgresult) {
598  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
599  ast_mutex_unlock(&pgsql_lock);
600  ast_config_destroy(cfg);
601  return NULL;
602  }
603 
604  ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", table, field->name, op, ast_str_buffer(escapebuf), escape);
605  while ((field = field->next)) {
606  escape = "";
607  if (!strchr(field->name, ' ')) {
608  op = " =";
609  escape = "";
610  } else {
611  op = "";
612  if (IS_SQL_LIKE_CLAUSE(field->name)) {
613  escape = ESCAPE_CLAUSE;
614  }
615  }
616 
617  ESCAPE_STRING(escapebuf, field->value);
618  if (pgresult) {
619  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
620  ast_mutex_unlock(&pgsql_lock);
621  ast_config_destroy(cfg);
622  return NULL;
623  }
624 
625  ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(escapebuf), escape);
626  }
627 
628  if (initfield && order_multi_row_results_by_initial_column) {
629  ast_str_append(&sql, 0, " ORDER BY %s", initfield);
630  }
631 
632  /* We now have our complete statement; Lets connect to the server and execute it. */
633  if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
634  ast_mutex_unlock(&pgsql_lock);
635  ast_config_destroy(cfg);
636  return NULL;
637  } else {
638  ExecStatusType result_status = PQresultStatus(result);
639  if (result_status != PGRES_COMMAND_OK
640  && result_status != PGRES_TUPLES_OK
641  && result_status != PGRES_NONFATAL_ERROR) {
642  ast_log(LOG_WARNING,
643  "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
644  ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
645  ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
646  PQresultErrorMessage(result), PQresStatus(result_status));
647  ast_mutex_unlock(&pgsql_lock);
648  ast_config_destroy(cfg);
649  return NULL;
650  }
651  }
652 
653  ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
654 
655  if ((num_rows = PQntuples(result)) > 0) {
656  int numFields = PQnfields(result);
657  int i = 0;
658  int rowIndex = 0;
659  char **fieldnames = NULL;
660 
661  ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
662 
663  if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
664  ast_mutex_unlock(&pgsql_lock);
665  ast_config_destroy(cfg);
666  return NULL;
667  }
668  for (i = 0; i < numFields; i++)
669  fieldnames[i] = PQfname(result, i);
670 
671  for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
672  var = NULL;
674  if (!cat) {
675  continue;
676  }
677  for (i = 0; i < numFields; i++) {
678  stringp = PQgetvalue(result, rowIndex, i);
679  while (stringp) {
680  chunk = strsep(&stringp, ";");
681  if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
682  if (initfield && !strcmp(initfield, fieldnames[i])) {
683  ast_category_rename(cat, chunk);
684  }
685  var = ast_variable_new(fieldnames[i], chunk, "");
686  ast_variable_append(cat, var);
687  }
688  }
689  }
690  ast_category_append(cfg, cat);
691  }
692  ast_free(fieldnames);
693  } else {
694  ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
695  }
696 
697  ast_mutex_unlock(&pgsql_lock);
698 
699  return cfg;
700 }
701 
702 static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
703  const char *lookup, const struct ast_variable *fields)
704 {
705  RAII_VAR(PGresult *, result, NULL, PQclear);
706  int numrows = 0, pgresult;
707  const struct ast_variable *field = fields;
708  struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
709  struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
710  struct tables *table;
711  struct columns *column = NULL;
712 
713  /*
714  * Ignore database from the extconfig.conf since it was
715  * configured by res_pgsql.conf.
716  */
717  database = dbname;
718 
719  if (!tablename) {
720  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
721  return -1;
722  }
723 
724  if (!(table = find_table(database, tablename))) {
725  ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
726  return -1;
727  }
728 
729  /*
730  * Must connect to the server before anything else as ESCAPE_STRING()
731  * uses pgsqlConn
732  */
733  ast_mutex_lock(&pgsql_lock);
734  if (!pgsql_reconnect(database)) {
735  ast_mutex_unlock(&pgsql_lock);
736  release_table(table);
737  return -1;
738  }
739 
740  /* Get the first parameter and first value in our list of passed paramater/value pairs */
741  if (!field) {
742  ast_log(LOG_WARNING,
743  "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
744  if (pgsqlConn) {
745  PQfinish(pgsqlConn);
746  pgsqlConn = NULL;
747  }
748  ast_mutex_unlock(&pgsql_lock);
749  release_table(table);
750  return -1;
751  }
752 
753  /* Check that the column exists in the table */
754  AST_LIST_TRAVERSE(&table->columns, column, list) {
755  if (strcmp(column->name, field->name) == 0) {
756  break;
757  }
758  }
759 
760  if (!column) {
761  ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename);
762  ast_mutex_unlock(&pgsql_lock);
763  release_table(table);
764  return -1;
765  }
766 
767  /* Create the first part of the query using the first parameter/value pairs we just extracted
768  If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
769 
770  ESCAPE_STRING(escapebuf, field->value);
771  if (pgresult) {
772  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
773  ast_mutex_unlock(&pgsql_lock);
774  release_table(table);
775  return -1;
776  }
777  ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, field->name, ast_str_buffer(escapebuf));
778 
779  while ((field = field->next)) {
780  if (!find_column(table, field->name)) {
781  ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename);
782  continue;
783  }
784 
785  ESCAPE_STRING(escapebuf, field->value);
786  if (pgresult) {
787  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
788  ast_mutex_unlock(&pgsql_lock);
789  release_table(table);
790  return -1;
791  }
792 
793  ast_str_append(&sql, 0, ", %s = '%s'", field->name, ast_str_buffer(escapebuf));
794  }
795  release_table(table);
796 
797  ESCAPE_STRING(escapebuf, lookup);
798  if (pgresult) {
799  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", lookup);
800  ast_mutex_unlock(&pgsql_lock);
801  return -1;
802  }
803 
804  ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
805 
806  ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
807 
808  /* We now have our complete statement; Lets connect to the server and execute it. */
809  if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
810  ast_mutex_unlock(&pgsql_lock);
811  return -1;
812  } else {
813  ExecStatusType result_status = PQresultStatus(result);
814  if (result_status != PGRES_COMMAND_OK
815  && result_status != PGRES_TUPLES_OK
816  && result_status != PGRES_NONFATAL_ERROR) {
817  ast_log(LOG_WARNING,
818  "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
819  ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
820  ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
821  PQresultErrorMessage(result), PQresStatus(result_status));
822  ast_mutex_unlock(&pgsql_lock);
823  return -1;
824  }
825  }
826 
827  numrows = atoi(PQcmdTuples(result));
828  ast_mutex_unlock(&pgsql_lock);
829 
830  ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
831 
832  /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
833  * An integer greater than zero indicates the number of rows affected
834  * Zero indicates that no records were updated
835  * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
836  */
837 
838  if (numrows >= 0)
839  return (int) numrows;
840 
841  return -1;
842 }
843 
844 static int update2_pgsql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
845 {
846  RAII_VAR(PGresult *, result, NULL, PQclear);
847  int numrows = 0, pgresult, first = 1;
848  struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
849  const struct ast_variable *field;
850  struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
851  struct ast_str *where = ast_str_thread_get(&where_buf, 100);
852  struct tables *table;
853 
854  /*
855  * Ignore database from the extconfig.conf since it was
856  * configured by res_pgsql.conf.
857  */
858  database = dbname;
859 
860  if (!tablename) {
861  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
862  return -1;
863  }
864 
865  if (!escapebuf || !sql || !where) {
866  /* Memory error, already handled */
867  return -1;
868  }
869 
870  if (!(table = find_table(database, tablename))) {
871  ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
872  return -1;
873  }
874 
875  /*
876  * Must connect to the server before anything else as ESCAPE_STRING()
877  * uses pgsqlConn
878  */
879  ast_mutex_lock(&pgsql_lock);
880  if (!pgsql_reconnect(database)) {
881  ast_mutex_unlock(&pgsql_lock);
882  release_table(table);
883  return -1;
884  }
885 
886  ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
887  ast_str_set(&where, 0, " WHERE");
888 
889  for (field = lookup_fields; field; field = field->next) {
890  if (!find_column(table, field->name)) {
891  ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", field->name, tablename, database);
892  ast_mutex_unlock(&pgsql_lock);
893  release_table(table);
894  return -1;
895  }
896 
897  ESCAPE_STRING(escapebuf, field->value);
898  if (pgresult) {
899  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
900  ast_mutex_unlock(&pgsql_lock);
901  release_table(table);
902  return -1;
903  }
904  ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", field->name, ast_str_buffer(escapebuf));
905  first = 0;
906  }
907 
908  if (first) {
909  ast_log(LOG_WARNING,
910  "PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
911  if (pgsqlConn) {
912  PQfinish(pgsqlConn);
913  pgsqlConn = NULL;
914  }
915  ast_mutex_unlock(&pgsql_lock);
916  release_table(table);
917  return -1;
918  }
919 
920  /* Now retrieve the columns to update */
921  first = 1;
922  for (field = update_fields; field; field = field->next) {
923  /* If the column is not within the table, then skip it */
924  if (!find_column(table, field->name)) {
925  ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", field->name, tablename, database);
926  continue;
927  }
928 
929  ESCAPE_STRING(escapebuf, field->value);
930  if (pgresult) {
931  ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
932  ast_mutex_unlock(&pgsql_lock);
933  release_table(table);
934  return -1;
935  }
936 
937  ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", field->name, ast_str_buffer(escapebuf));
938  first = 0;
939  }
940  release_table(table);
941 
942  ast_str_append(&sql, 0, "%s", ast_str_buffer(where));
943 
944  ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
945 
946  /* We now have our complete statement; Lets connect to the server and execute it. */
947  if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
948  ast_mutex_unlock(&pgsql_lock);
949  return -1;
950  }
951 
952  numrows = atoi(PQcmdTuples(result));
953  ast_mutex_unlock(&pgsql_lock);
954 
955  ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
956 
957  /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
958  * An integer greater than zero indicates the number of rows affected
959  * Zero indicates that no records were updated
960  * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
961  */
962 
963  if (numrows >= 0) {
964  return (int) numrows;
965  }
966 
967  return -1;
968 }
969 
970 static int store_pgsql(const char *database, const char *table, const struct ast_variable *fields)
971 {
972  RAII_VAR(PGresult *, result, NULL, PQclear);
973  int numrows;
974  struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
975  struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
976  struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
977  int pgresult;
978  const struct ast_variable *field = fields;
979 
980  /*
981  * Ignore database from the extconfig.conf since it was
982  * configured by res_pgsql.conf.
983  */
984  database = dbname;
985 
986  if (!table) {
987  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
988  return -1;
989  }
990 
991  /*
992  * Must connect to the server before anything else as ESCAPE_STRING()
993  * uses pgsqlConn
994  */
995  ast_mutex_lock(&pgsql_lock);
996  if (!pgsql_reconnect(database)) {
997  ast_mutex_unlock(&pgsql_lock);
998  return -1;
999  }
1000 
1001  /* Get the first parameter and first value in our list of passed paramater/value pairs */
1002  if (!field) {
1003  ast_log(LOG_WARNING,
1004  "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n");
1005  if (pgsqlConn) {
1006  PQfinish(pgsqlConn);
1007  pgsqlConn = NULL;
1008  }
1009  ast_mutex_unlock(&pgsql_lock);
1010  return -1;
1011  }
1012 
1013  /* Create the first part of the query using the first parameter/value pairs we just extracted
1014  If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
1015  ESCAPE_STRING(buf, field->name);
1016  ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf));
1017  ESCAPE_STRING(buf, field->value);
1018  ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
1019  while ((field = field->next)) {
1020  ESCAPE_STRING(buf, field->name);
1021  ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf));
1022  ESCAPE_STRING(buf, field->value);
1023  ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
1024  }
1025  ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
1026 
1027  ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql1));
1028 
1029  /* We now have our complete statement; Lets connect to the server and execute it. */
1030  if (pgsql_exec(database, table, ast_str_buffer(sql1), &result) != 0) {
1031  ast_mutex_unlock(&pgsql_lock);
1032  return -1;
1033  }
1034 
1035  numrows = atoi(PQcmdTuples(result));
1036  ast_mutex_unlock(&pgsql_lock);
1037 
1038  ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s.\n", table);
1039 
1040  /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
1041  * An integer greater than zero indicates the number of rows affected
1042  * Zero indicates that no records were updated
1043  * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
1044  */
1045 
1046  if (numrows >= 0) {
1047  return numrows;
1048  }
1049 
1050  return -1;
1051 }
1052 
1053 static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
1054 {
1055  RAII_VAR(PGresult *, result, NULL, PQclear);
1056  int numrows = 0;
1057  int pgresult;
1058  struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
1059  struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
1060  const struct ast_variable *field;
1061 
1062  /*
1063  * Ignore database from the extconfig.conf since it was
1064  * configured by res_pgsql.conf.
1065  */
1066  database = dbname;
1067 
1068  if (!table) {
1069  ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
1070  return -1;
1071  }
1072 
1073  /*
1074  * Must connect to the server before anything else as ESCAPE_STRING()
1075  * uses pgsqlConn
1076  */
1077  ast_mutex_lock(&pgsql_lock);
1078  if (!pgsql_reconnect(database)) {
1079  ast_mutex_unlock(&pgsql_lock);
1080  return -1;
1081  }
1082 
1083  /* Get the first parameter and first value in our list of passed paramater/value pairs */
1084  if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) {
1085  ast_log(LOG_WARNING,
1086  "PostgreSQL RealTime: Realtime destroy requires at least 1 parameter and 1 value to search on.\n");
1087  if (pgsqlConn) {
1088  PQfinish(pgsqlConn);
1089  pgsqlConn = NULL;
1090  }
1091  ast_mutex_unlock(&pgsql_lock);
1092  return -1;
1093  }
1094 
1095  /* Create the first part of the query using the first parameter/value pairs we just extracted
1096  If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
1097 
1098  ESCAPE_STRING(buf1, keyfield);
1099  ESCAPE_STRING(buf2, lookup);
1100  ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2));
1101  for (field = fields; field; field = field->next) {
1102  ESCAPE_STRING(buf1, field->name);
1103  ESCAPE_STRING(buf2, field->value);
1104  ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2));
1105  }
1106 
1107  ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
1108 
1109  /* We now have our complete statement; Lets connect to the server and execute it. */
1110  if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
1111  ast_mutex_unlock(&pgsql_lock);
1112  return -1;
1113  }
1114 
1115  numrows = atoi(PQcmdTuples(result));
1116  ast_mutex_unlock(&pgsql_lock);
1117 
1118  ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table);
1119 
1120  /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
1121  * An integer greater than zero indicates the number of rows affected
1122  * Zero indicates that no records were updated
1123  * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
1124  */
1125 
1126  if (numrows >= 0)
1127  return (int) numrows;
1128 
1129  return -1;
1130 }
1131 
1132 
1133 static struct ast_config *config_pgsql(const char *database, const char *table,
1134  const char *file, struct ast_config *cfg,
1135  struct ast_flags flags, const char *suggested_incl, const char *who_asked)
1136 {
1137  RAII_VAR(PGresult *, result, NULL, PQclear);
1138  long num_rows;
1139  struct ast_variable *new_v;
1140  struct ast_category *cur_cat = NULL;
1141  struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
1142  char last[80];
1143  int last_cat_metric = 0;
1144 
1145  last[0] = '\0';
1146 
1147  /*
1148  * Ignore database from the extconfig.conf since it is
1149  * configured by res_pgsql.conf.
1150  */
1151  database = dbname;
1152 
1153  if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
1154  ast_log(LOG_WARNING, "PostgreSQL RealTime: Cannot configure myself.\n");
1155  return NULL;
1156  }
1157 
1158  ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
1159  "WHERE filename='%s' and commented=0 "
1160  "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
1161 
1162  ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
1163 
1164  ast_mutex_lock(&pgsql_lock);
1165 
1166  /* We now have our complete statement; Lets connect to the server and execute it. */
1167  if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
1168  ast_mutex_unlock(&pgsql_lock);
1169  return NULL;
1170  }
1171 
1172  if ((num_rows = PQntuples(result)) > 0) {
1173  int rowIndex = 0;
1174 
1175  ast_debug(1, "PostgreSQL RealTime: Found %ld rows.\n", num_rows);
1176 
1177  for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
1178  char *field_category = PQgetvalue(result, rowIndex, 0);
1179  char *field_var_name = PQgetvalue(result, rowIndex, 1);
1180  char *field_var_val = PQgetvalue(result, rowIndex, 2);
1181  char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
1182  if (!strcmp(field_var_name, "#include")) {
1183  if (!ast_config_internal_load(field_var_val, cfg, flags, "", who_asked)) {
1184  ast_mutex_unlock(&pgsql_lock);
1185  return NULL;
1186  }
1187  continue;
1188  }
1189 
1190  if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
1191  cur_cat = ast_category_new_dynamic(field_category);
1192  if (!cur_cat) {
1193  break;
1194  }
1195  ast_copy_string(last, field_category, sizeof(last));
1196  last_cat_metric = atoi(field_cat_metric);
1197  ast_category_append(cfg, cur_cat);
1198  }
1199  new_v = ast_variable_new(field_var_name, field_var_val, "");
1200  ast_variable_append(cur_cat, new_v);
1201  }
1202  } else {
1203  ast_log(LOG_WARNING,
1204  "PostgreSQL RealTime: Could not find config '%s' in database.\n", file);
1205  }
1206 
1207  ast_mutex_unlock(&pgsql_lock);
1208 
1209  return cfg;
1210 }
1211 
1212 static int require_pgsql(const char *database, const char *tablename, va_list ap)
1213 {
1214  struct columns *column;
1215  struct tables *table;
1216  char *elm;
1217  int type, res = 0;
1218  unsigned int size;
1219 
1220  /*
1221  * Ignore database from the extconfig.conf since it was
1222  * configured by res_pgsql.conf.
1223  */
1224  database = dbname;
1225 
1226  table = find_table(database, tablename);
1227  if (!table) {
1228  ast_log(LOG_WARNING, "Table %s not found in database. This table should exist if you're using realtime.\n", tablename);
1229  return -1;
1230  }
1231 
1232  while ((elm = va_arg(ap, char *))) {
1233  type = va_arg(ap, require_type);
1234  size = va_arg(ap, unsigned int);
1235  AST_LIST_TRAVERSE(&table->columns, column, list) {
1236  if (strcmp(column->name, elm) == 0) {
1237  /* Char can hold anything, as long as it is large enough */
1238  if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0 || strncmp(column->type, "text", 4) == 0)) {
1239  if (column->len != -1 && (size > column->len)) {
1240  ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
1241  res = -1;
1242  }
1243  } else if (strncmp(column->type, "int", 3) == 0) {
1244  int typesize = atoi(column->type + 3);
1245  /* Integers can hold only other integers */
1246  if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
1247  type == RQ_INTEGER4 || type == RQ_UINTEGER4 ||
1248  type == RQ_INTEGER3 || type == RQ_UINTEGER3 ||
1249  type == RQ_UINTEGER2) && typesize == 2) {
1250  ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
1251  res = -1;
1252  } else if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
1253  type == RQ_UINTEGER4) && typesize == 4) {
1254  ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
1255  res = -1;
1256  } else if (type == RQ_CHAR || type == RQ_DATETIME || type == RQ_FLOAT || type == RQ_DATE) {
1257  ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n",
1258  column->name,
1259  type == RQ_CHAR ? "char" :
1260  type == RQ_DATETIME ? "datetime" :
1261  type == RQ_DATE ? "date" :
1262  type == RQ_FLOAT ? "float" :
1263  "a rather stiff drink ",
1264  size, column->type);
1265  res = -1;
1266  }
1267  } else if (strncmp(column->type, "float", 5) == 0) {
1268  if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
1269  ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
1270  res = -1;
1271  }
1272  } else if (strncmp(column->type, "timestamp", 9) == 0) {
1273  if (type != RQ_DATETIME && type != RQ_DATE) {
1274  ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
1275  res = -1;
1276  }
1277  } else { /* There are other types that no module implements yet */
1278  ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
1279  res = -1;
1280  }
1281  break;
1282  }
1283  }
1284 
1285  if (!column) {
1286  if (requirements == RQ_WARN) {
1287  ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
1288  res = -1;
1289  } else {
1290  struct ast_str *sql = ast_str_create(100);
1291  char fieldtype[20];
1292  PGresult *result;
1293 
1294  if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
1295  /* Size is minimum length; make it at least 50% greater,
1296  * just to be sure, because PostgreSQL doesn't support
1297  * resizing columns. */
1298  snprintf(fieldtype, sizeof(fieldtype), "CHAR(%u)",
1299  size < 15 ? size * 2 :
1300  (size * 3 / 2 > 255) ? 255 : size * 3 / 2);
1301  } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
1302  snprintf(fieldtype, sizeof(fieldtype), "INT2");
1303  } else if (type == RQ_UINTEGER2 || type == RQ_INTEGER3 || type == RQ_UINTEGER3 || type == RQ_INTEGER4) {
1304  snprintf(fieldtype, sizeof(fieldtype), "INT4");
1305  } else if (type == RQ_UINTEGER4 || type == RQ_INTEGER8) {
1306  snprintf(fieldtype, sizeof(fieldtype), "INT8");
1307  } else if (type == RQ_UINTEGER8) {
1308  /* No such type on PostgreSQL */
1309  snprintf(fieldtype, sizeof(fieldtype), "CHAR(20)");
1310  } else if (type == RQ_FLOAT) {
1311  snprintf(fieldtype, sizeof(fieldtype), "FLOAT8");
1312  } else if (type == RQ_DATE) {
1313  snprintf(fieldtype, sizeof(fieldtype), "DATE");
1314  } else if (type == RQ_DATETIME) {
1315  snprintf(fieldtype, sizeof(fieldtype), "TIMESTAMP");
1316  } else {
1317  ast_log(LOG_ERROR, "Unrecognized request type %d\n", type);
1318  ast_free(sql);
1319  continue;
1320  }
1321  ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype);
1322  ast_debug(1, "About to lock pgsql_lock (running alter on table '%s' to add column '%s')\n", tablename, elm);
1323 
1324  ast_mutex_lock(&pgsql_lock);
1325  ast_debug(1, "About to run ALTER query on table '%s' to add column '%s'\n", tablename, elm);
1326 
1327  if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
1328  ast_mutex_unlock(&pgsql_lock);
1329  release_table(table);
1330  return -1;
1331  }
1332 
1333  ast_debug(1, "Finished running ALTER query on table '%s'\n", tablename);
1334  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
1335  ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1336  }
1337  PQclear(result);
1338  ast_mutex_unlock(&pgsql_lock);
1339 
1340  ast_free(sql);
1341  }
1342  }
1343  }
1344  release_table(table);
1345  return res;
1346 }
1347 
1348 static int unload_pgsql(const char *database, const char *tablename)
1349 {
1350  struct tables *cur;
1351 
1352  /*
1353  * Ignore database from the extconfig.conf since it was
1354  * configured by res_pgsql.conf.
1355  */
1356  database = dbname;
1357 
1358  ast_debug(2, "About to lock table cache list\n");
1360  ast_debug(2, "About to traverse table cache list\n");
1362  if (strcmp(cur->name, tablename) == 0) {
1363  ast_debug(2, "About to remove matching cache entry\n");
1365  ast_debug(2, "About to destroy matching cache entry\n");
1366  destroy_table(cur);
1367  ast_debug(1, "Cache entry '%s@%s' destroyed\n", tablename, database);
1368  break;
1369  }
1370  }
1373  ast_debug(2, "About to return\n");
1374  return cur ? 0 : -1;
1375 }
1376 
1377 static struct ast_config_engine pgsql_engine = {
1378  .name = "pgsql",
1379  .load_func = config_pgsql,
1380  .realtime_func = realtime_pgsql,
1381  .realtime_multi_func = realtime_multi_pgsql,
1382  .store_func = store_pgsql,
1383  .destroy_func = destroy_pgsql,
1384  .update_func = update_pgsql,
1385  .update2_func = update2_pgsql,
1386  .require_func = require_pgsql,
1387  .unload_func = unload_pgsql,
1388 };
1389 
1390 static int load_module(void)
1391 {
1392  if(!parse_config(0))
1393  return AST_MODULE_LOAD_DECLINE;
1394 
1395  ast_config_engine_register(&pgsql_engine);
1396 
1397  ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
1398 
1399  return 0;
1400 }
1401 
1402 static int unload_module(void)
1403 {
1404  struct tables *table;
1405  /* Acquire control before doing anything to the module itself. */
1406  ast_mutex_lock(&pgsql_lock);
1407 
1408  if (pgsqlConn) {
1409  PQfinish(pgsqlConn);
1410  pgsqlConn = NULL;
1411  }
1412  ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
1413  ast_config_engine_deregister(&pgsql_engine);
1414 
1415  /* Unlock so something else can destroy the lock. */
1416  ast_mutex_unlock(&pgsql_lock);
1417 
1418  /* Destroy cached table info */
1420  while ((table = AST_LIST_REMOVE_HEAD(&psql_tables, list))) {
1421  destroy_table(table);
1422  }
1424 
1425  return 0;
1426 }
1427 
1428 static int reload(void)
1429 {
1430  parse_config(1);
1431 
1432  return 0;
1433 }
1434 
1435 static int parse_config(int is_reload)
1436 {
1437  struct ast_config *config;
1438  const char *s;
1439  struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1440 
1441  config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags);
1442  if (config == CONFIG_STATUS_FILEUNCHANGED) {
1443  if (is_reload && pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
1444  ast_log(LOG_WARNING, "PostgreSQL RealTime: Not connected\n");
1445  }
1446  return 0;
1447  }
1448 
1449  if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
1450  ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF);
1451  return 0;
1452  }
1453 
1454  ast_mutex_lock(&pgsql_lock);
1455 
1456  /* XXX: Why would we do this before we're ready to establish a new connection? */
1457  if (pgsqlConn) {
1458  PQfinish(pgsqlConn);
1459  pgsqlConn = NULL;
1460  }
1461 
1462  if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
1463  ast_log(LOG_WARNING,
1464  "PostgreSQL RealTime: No database user found, using 'asterisk' as default.\n");
1465  strcpy(dbuser, "asterisk");
1466  } else {
1467  ast_copy_string(dbuser, s, sizeof(dbuser));
1468  }
1469 
1470  if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
1471  ast_log(LOG_WARNING,
1472  "PostgreSQL RealTime: No database password found, using 'asterisk' as default.\n");
1473  strcpy(dbpass, "asterisk");
1474  } else {
1475  ast_copy_string(dbpass, s, sizeof(dbpass));
1476  }
1477 
1478  if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
1479  ast_log(LOG_WARNING,
1480  "PostgreSQL RealTime: No database host found, using localhost via socket.\n");
1481  dbhost[0] = '\0';
1482  } else {
1483  ast_copy_string(dbhost, s, sizeof(dbhost));
1484  }
1485 
1486  if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
1487  ast_log(LOG_WARNING,
1488  "PostgreSQL RealTime: No database name found, using 'asterisk' as default.\n");
1489  strcpy(dbname, "asterisk");
1490  } else {
1491  ast_copy_string(dbname, s, sizeof(dbname));
1492  }
1493 
1494  if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
1495  ast_log(LOG_WARNING,
1496  "PostgreSQL RealTime: No database port found, using 5432 as default.\n");
1497  dbport = 5432;
1498  } else {
1499  dbport = atoi(s);
1500  }
1501 
1502  if (!(s = ast_variable_retrieve(config, "general", "dbappname"))) {
1503  dbappname[0] = '\0';
1504  } else {
1505  ast_copy_string(dbappname, s, sizeof(dbappname));
1506  }
1507 
1508  if (!ast_strlen_zero(dbhost)) {
1509  /* No socket needed */
1510  } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) {
1511  ast_log(LOG_WARNING,
1512  "PostgreSQL RealTime: No database socket found, using '/tmp/.s.PGSQL.%d' as default.\n", dbport);
1513  strcpy(dbsock, "/tmp");
1514  } else {
1515  ast_copy_string(dbsock, s, sizeof(dbsock));
1516  }
1517 
1518  if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
1519  ast_log(LOG_WARNING,
1520  "PostgreSQL RealTime: no requirements setting found, using 'warn' as default.\n");
1521  requirements = RQ_WARN;
1522  } else if (!strcasecmp(s, "createclose")) {
1523  requirements = RQ_CREATECLOSE;
1524  } else if (!strcasecmp(s, "createchar")) {
1525  requirements = RQ_CREATECHAR;
1526  }
1527 
1528  /* Result set ordering is enabled by default */
1529  s = ast_variable_retrieve(config, "general", "order_multi_row_results_by_initial_column");
1530  order_multi_row_results_by_initial_column = !s || ast_true(s);
1531 
1532  ast_config_destroy(config);
1533 
1534  if (DEBUG_ATLEAST(1)) {
1535  if (!ast_strlen_zero(dbhost)) {
1536  ast_log(LOG_DEBUG, "PostgreSQL RealTime Host: %s\n", dbhost);
1537  ast_log(LOG_DEBUG, "PostgreSQL RealTime Port: %i\n", dbport);
1538  } else {
1539  ast_log(LOG_DEBUG, "PostgreSQL RealTime Socket: %s\n", dbsock);
1540  }
1541  ast_log(LOG_DEBUG, "PostgreSQL RealTime User: %s\n", dbuser);
1542  ast_log(LOG_DEBUG, "PostgreSQL RealTime Password: %s\n", dbpass);
1543  ast_log(LOG_DEBUG, "PostgreSQL RealTime DBName: %s\n", dbname);
1544  }
1545 
1546  if (!pgsql_reconnect(NULL)) {
1547  ast_log(LOG_WARNING,
1548  "PostgreSQL RealTime: Couldn't establish connection. Check debug.\n");
1549  ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQerrorMessage(pgsqlConn));
1550  }
1551 
1552  ast_verb(2, "PostgreSQL RealTime reloaded.\n");
1553 
1554  /* Done reloading. Release lock so others can now use driver. */
1555  ast_mutex_unlock(&pgsql_lock);
1556 
1557  return 1;
1558 }
1559 
1560 static int pgsql_reconnect(const char *database)
1561 {
1562  char my_database[50];
1563 
1564  ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
1565 
1566  /* mutex lock should have been locked before calling this function. */
1567 
1568  if (pgsqlConn) {
1569  if (PQstatus(pgsqlConn) == CONNECTION_OK) {
1570  /* We're good? */
1571  return 1;
1572  }
1573 
1574  PQfinish(pgsqlConn);
1575  pgsqlConn = NULL;
1576  }
1577 
1578  /* DB password can legitimately be 0-length */
1579  if ((!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(my_database)) {
1580  struct ast_str *conn_info = ast_str_create(128);
1581 
1582  if (!conn_info) {
1583  ast_log(LOG_ERROR, "PostgreSQL RealTime: Failed to allocate memory for connection string.\n");
1584  return 0;
1585  }
1586 
1587  ast_str_set(&conn_info, 0, "host=%s port=%d dbname=%s user=%s",
1588  S_OR(dbhost, dbsock), dbport, my_database, dbuser);
1589 
1590  if (!ast_strlen_zero(dbappname)) {
1591  ast_str_append(&conn_info, 0, " application_name=%s", dbappname);
1592  }
1593 
1594  if (!ast_strlen_zero(dbpass)) {
1595  ast_str_append(&conn_info, 0, " password=%s", dbpass);
1596  }
1597 
1598  pgsqlConn = PQconnectdb(ast_str_buffer(conn_info));
1599  ast_free(conn_info);
1600  conn_info = NULL;
1601 
1602  ast_debug(1, "pgsqlConn=%p\n", pgsqlConn);
1603  if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
1604  ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n");
1605  connect_time = time(NULL);
1606  version = PQserverVersion(pgsqlConn);
1607  return 1;
1608  } else {
1609  ast_log(LOG_ERROR,
1610  "PostgreSQL RealTime: Failed to connect database %s on %s: %s\n",
1611  my_database, dbhost, PQresultErrorMessage(NULL));
1612  return 0;
1613  }
1614  } else {
1615  ast_debug(1, "PostgreSQL RealTime: One or more of the parameters in the config does not pass our validity checks.\n");
1616  return 1;
1617  }
1618 }
1619 
1620 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1621 {
1622  struct tables *cur;
1623  int l, which;
1624  char *ret = NULL;
1625 
1626  switch (cmd) {
1627  case CLI_INIT:
1628  e->command = "realtime show pgsql cache";
1629  e->usage =
1630  "Usage: realtime show pgsql cache [<table>]\n"
1631  " Shows table cache for the PostgreSQL RealTime driver\n";
1632  return NULL;
1633  case CLI_GENERATE:
1634  if (a->argc != 4) {
1635  return NULL;
1636  }
1637  l = strlen(a->word);
1638  which = 0;
1640  AST_LIST_TRAVERSE(&psql_tables, cur, list) {
1641  if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
1642  ret = ast_strdup(cur->name);
1643  break;
1644  }
1645  }
1647  return ret;
1648  }
1649 
1650  if (a->argc == 4) {
1651  /* List of tables */
1653  AST_LIST_TRAVERSE(&psql_tables, cur, list) {
1654  ast_cli(a->fd, "%s\n", cur->name);
1655  }
1657  } else if (a->argc == 5) {
1658  /* List of columns */
1659  if ((cur = find_table(NULL, a->argv[4]))) {
1660  struct columns *col;
1661  ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[4]);
1662  ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s %-8.8s\n", "Name", "Type", "Len", "Nullable");
1663  AST_LIST_TRAVERSE(&cur->columns, col, list) {
1664  ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
1665  }
1666  release_table(cur);
1667  } else {
1668  ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
1669  }
1670  }
1671  return 0;
1672 }
1673 
1674 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1675 {
1676  char connection_info[256];
1677  char credentials[100] = "";
1678  char buf[376]; /* 256+100+"Connected to "+" for "+NULL */
1679  int is_connected = 0, ctimesec = time(NULL) - connect_time;
1680 
1681  switch (cmd) {
1682  case CLI_INIT:
1683  e->command = "realtime show pgsql status";
1684  e->usage =
1685  "Usage: realtime show pgsql status\n"
1686  " Shows connection information for the PostgreSQL RealTime driver\n";
1687  return NULL;
1688  case CLI_GENERATE:
1689  return NULL;
1690  }
1691 
1692  if (a->argc != 4)
1693  return CLI_SHOWUSAGE;
1694 
1695  if (!ast_strlen_zero(dbhost))
1696  snprintf(connection_info, sizeof(connection_info), "%s@%s, port %d", dbname, dbhost, dbport);
1697  else if (!ast_strlen_zero(dbsock))
1698  snprintf(connection_info, sizeof(connection_info), "%s on socket file %s", dbname, dbsock);
1699  else
1700  snprintf(connection_info, sizeof(connection_info), "%s@%s", dbname, dbhost);
1701 
1702  if (!ast_strlen_zero(dbuser))
1703  snprintf(credentials, sizeof(credentials), " with username %s", dbuser);
1704 
1705  ast_mutex_lock(&pgsql_lock);
1706  is_connected = (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK);
1707  ast_mutex_unlock(&pgsql_lock);
1708 
1709  if (is_connected) {
1710  snprintf(buf, sizeof(buf), "Connected to %s%s for ", connection_info, credentials);
1711  ast_cli_print_timestr_fromseconds(a->fd, ctimesec, buf);
1712  return CLI_SUCCESS;
1713  } else {
1714  ast_cli(a->fd, "Unable to connect %s%s\n", connection_info, credentials);
1715  return CLI_FAILURE;
1716  }
1717 }
1718 
1719 /* needs usecount semantics defined */
1720 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL RealTime Configuration Driver",
1721  .support_level = AST_MODULE_SUPPORT_EXTENDED,
1722  .load = load_module,
1723  .unload = unload_module,
1724  .reload = reload,
1725  .load_pri = AST_MODPRI_REALTIME_DRIVER,
1726  .requires = "extconfig",
1727 );
struct ast_variable * next
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
require_type
Types used in ast_realtime_require_field.
char * ast_realtime_decode_chunk(char *chunk)
Remove standard encoding from realtime values, which ensures that a semicolon embedded within a singl...
Definition: main/config.c:3795
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
descriptor for a cli entry.
Definition: cli.h:171
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define ast_rwlock_init(rwlock)
wrapper for rwlock with tracking enabled
Definition: lock.h:224
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
Structure for variables, used for configurations and for channel variables.
static int _pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
Helper function for pgsql_exec. For running queries, use pgsql_exec()
int ast_config_engine_deregister(struct ast_config_engine *del)
Deregister config engine.
Definition: main/config.c:3173
int ast_config_engine_register(struct ast_config_engine *newconfig)
Register config engine.
Definition: main/config.c:3157
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_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
Configuration engine structure, used to define realtime drivers.
Utility functions.
void ast_category_append(struct ast_config *config, struct ast_category *category)
Appends a category to a config.
Definition: extconf.c:2833
#define ast_category_new_anonymous()
Create a nameless category that is not backed by a file.
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.
ast_mutex_t lock
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
struct ast_config * ast_config_new(void)
Create a new base configuration structure.
Definition: extconf.c:3274
Core PBX routines and definitions.
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:291
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Definition: linkedlists.h:225
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
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_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
char * command
Definition: cli.h:186
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_category_new_dynamic(name)
Create a category that is not backed by a file.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static int pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
Do a postgres query, with reconnection support.
Structure used to handle boolean flags.
Definition: utils.h:199
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:3056
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
Definition: linkedlists.h:681
Structure for rwlock and tracking information.
Definition: lock.h:157
Standard Command Line Interface.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
int ast_rq_is_int(require_type type)
Check if require type is an integer type.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659