Wed Jan 8 2020 09:49:59

Asterisk developer's documentation


cdr_adaptive_odbc.c File Reference

Adaptive ODBC CDR backend. More...

#include "asterisk.h"
#include <sys/types.h>
#include <time.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#include "asterisk/config.h"
#include "asterisk/channel.h"
#include "asterisk/lock.h"
#include "asterisk/linkedlists.h"
#include "asterisk/res_odbc.h"
#include "asterisk/cdr.h"
#include "asterisk/module.h"

Go to the source code of this file.

Data Structures

struct  columns
 
struct  tables::odbc_columns
 
struct  odbc_tables
 
struct  tables
 

Macros

#define CONFIG   "cdr_adaptive_odbc.conf"
 
#define LENGTHEN_BUF1(size)
 
#define LENGTHEN_BUF2(size)
 

Functions

static void __reg_module (void)
 
static void __unreg_module (void)
 
static int free_config (void)
 
static SQLHSTMT generic_prepare (struct odbc_obj *obj, void *data)
 
static int load_config (void)
 
static int load_module (void)
 
static int odbc_log (struct ast_cdr *cdr)
 
static int reload (void)
 
static int unload_module (void)
 

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Adaptive ODBC CDR backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "ac1f6a56484a8820659555499174e588" , .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, }
 
static struct ast_module_infoast_module_info = &__mod_info
 
static int maxsize = 512
 
static int maxsize2 = 512
 
static const char name [] = "Adaptive ODBC"
 
static struct odbc_tables odbc_tables = { .first = NULL, .last = NULL, .lock = { PTHREAD_RWLOCK_INITIALIZER , NULL, 1 } , }
 

Detailed Description

Macro Definition Documentation

#define CONFIG   "cdr_adaptive_odbc.conf"

Definition at line 51 of file cdr_adaptive_odbc.c.

Referenced by load_config().

#define LENGTHEN_BUF1 (   size)

Definition at line 316 of file cdr_adaptive_odbc.c.

Referenced by odbc_log().

#define LENGTHEN_BUF2 (   size)

Definition at line 330 of file cdr_adaptive_odbc.c.

Referenced by odbc_log().

Function Documentation

static void __reg_module ( void  )
static

Definition at line 773 of file cdr_adaptive_odbc.c.

static void __unreg_module ( void  )
static

Definition at line 773 of file cdr_adaptive_odbc.c.

static int free_config ( void  )
static

Definition at line 270 of file cdr_adaptive_odbc.c.

References ast_free, AST_LIST_REMOVE_HEAD, AST_RWLIST_REMOVE_HEAD, tables::columns, and table.

Referenced by reload(), and unload_module().

271 {
272  struct tables *table;
273  struct columns *entry;
274  while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
275  while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
276  ast_free(entry);
277  }
278  ast_free(table);
279  }
280  return 0;
281 }
static char * table
Definition: cdr_odbc.c:50
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:818
struct tables::odbc_columns columns
struct columns::@71 list
#define ast_free(a)
Definition: astmm.h:97
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:829
static SQLHSTMT generic_prepare ( struct odbc_obj obj,
void *  data 
)
static

Definition at line 283 of file cdr_adaptive_odbc.c.

References ast_log(), odbc_obj::con, and LOG_WARNING.

Referenced by odbc_log().

284 {
285  int res, i;
286  SQLHSTMT stmt;
287  SQLINTEGER nativeerror = 0, numfields = 0;
288  SQLSMALLINT diagbytes = 0;
289  unsigned char state[10], diagnostic[256];
290 
291  res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
292  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
293  ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
294  return NULL;
295  }
296 
297  res = SQLPrepare(stmt, (unsigned char *) data, SQL_NTS);
298  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
299  ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *) data);
300  SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
301  for (i = 0; i < numfields; i++) {
302  SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
303  ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
304  if (i > 10) {
305  ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
306  break;
307  }
308  }
309  SQLFreeHandle (SQL_HANDLE_STMT, stmt);
310  return NULL;
311  }
312 
313  return stmt;
314 }
SQLHDBC con
Definition: res_odbc.h:48
#define LOG_WARNING
Definition: logger.h:144
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
static int load_config ( void  )
static

Definition at line 81 of file cdr_adaptive_odbc.c.

References ast_calloc, ast_category_browse(), ast_config_destroy(), ast_config_load, ast_copy_string(), ast_free, AST_LIST_FIRST, AST_LIST_INSERT_TAIL, ast_log(), ast_odbc_release_obj(), ast_odbc_request_obj, AST_RWLIST_INSERT_TAIL, ast_strdupa, ast_strip(), ast_strlen_zero(), ast_true(), ast_variable_browse(), ast_variable_retrieve(), ast_verb, columns::cdrname, tables::columns, odbc_obj::con, CONFIG, CONFIG_STATUS_FILEINVALID, tables::connection, columns::decimals, columns::filtervalue, LOG_ERROR, LOG_NOTICE, LOG_WARNING, columns::name, ast_variable::name, ast_variable::next, columns::nullable, columns::octetlen, columns::radix, columns::size, columns::staticvalue, tables::table, columns::type, usegmtime, tables::usegmtime, ast_variable::value, and var.

Referenced by load_module(), and reload().

82 {
83  struct ast_config *cfg;
84  struct ast_variable *var;
85  const char *tmp, *catg;
86  struct tables *tableptr;
87  struct columns *entry;
88  struct odbc_obj *obj;
89  char columnname[80];
90  char connection[40];
91  char table[40];
92  int lenconnection, lentable, usegmtime = 0;
93  SQLLEN sqlptr;
94  int res = 0;
95  SQLHSTMT stmt = NULL;
96  struct ast_flags config_flags = { 0 }; /* Part of our config comes from the database */
97 
98  cfg = ast_config_load(CONFIG, config_flags);
99  if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
100  ast_log(LOG_WARNING, "Unable to load " CONFIG ". No adaptive ODBC CDRs.\n");
101  return -1;
102  }
103 
104  for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
105  var = ast_variable_browse(cfg, catg);
106  if (!var)
107  continue;
108 
109  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
110  ast_log(LOG_WARNING, "No connection parameter found in '%s'. Skipping.\n", catg);
111  continue;
112  }
113  ast_copy_string(connection, tmp, sizeof(connection));
114  lenconnection = strlen(connection);
115 
116  if (!ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "usegmtime"))) {
117  usegmtime = ast_true(tmp);
118  }
119 
120  /* When loading, we want to be sure we can connect. */
121  obj = ast_odbc_request_obj(connection, 1);
122  if (!obj) {
123  ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ". Check res_odbc.conf.\n", connection, catg);
124  continue;
125  }
126 
127  if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
128  ast_log(LOG_NOTICE, "No table name found. Assuming 'cdr'.\n");
129  tmp = "cdr";
130  }
131  ast_copy_string(table, tmp, sizeof(table));
132  lentable = strlen(table);
133 
134  res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
135  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
136  ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
138  continue;
139  }
140 
141  res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
142  if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
143  ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'. Skipping.\n", connection);
145  continue;
146  }
147 
148  tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
149  if (!tableptr) {
150  ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
152  res = -1;
153  break;
154  }
155 
156  tableptr->usegmtime = usegmtime;
157  tableptr->connection = (char *)tableptr + sizeof(*tableptr);
158  tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
159  ast_copy_string(tableptr->connection, connection, lenconnection + 1);
160  ast_copy_string(tableptr->table, table, lentable + 1);
161 
162  ast_verb(3, "Found adaptive CDR table %s@%s.\n", tableptr->table, tableptr->connection);
163 
164  /* Check for filters first */
165  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
166  if (strncmp(var->name, "filter", 6) == 0) {
167  char *cdrvar = ast_strdupa(var->name + 6);
168  cdrvar = ast_strip(cdrvar);
169  ast_verb(3, "Found filter %s for cdr variable %s in %s@%s\n", var->value, cdrvar, tableptr->table, tableptr->connection);
170 
171  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(cdrvar) + 1 + strlen(var->value) + 1);
172  if (!entry) {
173  ast_log(LOG_ERROR, "Out of memory creating filter entry for CDR variable '%s' in table '%s' on connection '%s'\n", cdrvar, table, connection);
174  res = -1;
175  break;
176  }
177 
178  /* NULL column entry means this isn't a column in the database */
179  entry->name = NULL;
180  entry->cdrname = (char *)entry + sizeof(*entry);
181  entry->filtervalue = (char *)entry + sizeof(*entry) + strlen(cdrvar) + 1;
182  strcpy(entry->cdrname, cdrvar);
183  strcpy(entry->filtervalue, var->value);
184 
185  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
186  }
187  }
188 
189  while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
190  char *cdrvar = "", *staticvalue = "";
191 
192  SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
193 
194  /* Is there an alias for this column? */
195 
196  /* NOTE: This seems like a non-optimal parse method, but I'm going
197  * for user configuration readability, rather than fast parsing. We
198  * really don't parse this file all that often, anyway.
199  */
200  for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
201  if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, columnname) == 0) {
202  char *alias = ast_strdupa(var->name + 5);
203  cdrvar = ast_strip(alias);
204  ast_verb(3, "Found alias %s for column %s in %s@%s\n", cdrvar, columnname, tableptr->table, tableptr->connection);
205  break;
206  } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, columnname) == 0) {
207  char *item = ast_strdupa(var->name + 6);
208  item = ast_strip(item);
209  if (item[0] == '"' && item[strlen(item) - 1] == '"') {
210  /* Remove surrounding quotes */
211  item[strlen(item) - 1] = '\0';
212  item++;
213  }
214  staticvalue = item;
215  }
216  }
217 
218  entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1);
219  if (!entry) {
220  ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
221  res = -1;
222  break;
223  }
224  entry->name = (char *)entry + sizeof(*entry);
225  strcpy(entry->name, columnname);
226 
227  if (!ast_strlen_zero(cdrvar)) {
228  entry->cdrname = entry->name + strlen(columnname) + 1;
229  strcpy(entry->cdrname, cdrvar);
230  } else { /* Point to same place as the column name */
231  entry->cdrname = (char *)entry + sizeof(*entry);
232  }
233 
234  if (!ast_strlen_zero(staticvalue)) {
235  entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
236  strcpy(entry->staticvalue, staticvalue);
237  }
238 
239  SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
240  SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
241  SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
242  SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
243  SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
244  SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
245 
246  /* Specification states that the octenlen should be the maximum number of bytes
247  * returned in a char or binary column, but it seems that some drivers just set
248  * it to NULL. (Bad Postgres! No biscuit!) */
249  if (entry->octetlen == 0)
250  entry->octetlen = entry->size;
251 
252  ast_verb(10, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (long) entry->size, (long) entry->octetlen, entry->decimals, entry->radix);
253  /* Insert column info into column list */
254  AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
255  res = 0;
256  }
257 
258  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
260 
261  if (AST_LIST_FIRST(&(tableptr->columns)))
262  AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
263  else
264  ast_free(tableptr);
265  }
266  ast_config_destroy(cfg);
267  return res;
268 }
SQLHDBC con
Definition: res_odbc.h:48
SQLSMALLINT radix
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:420
const char * ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
Gets a variable.
Definition: config.c:625
#define LOG_WARNING
Definition: logger.h:144
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category)
Goes through variables.
Definition: config.c:597
SQLSMALLINT nullable
Structure for variables, used for configurations and for channel variables.
Definition: config.h:75
#define var
Definition: ast_expr2f.c:606
char * connection
char * filtervalue
SQLINTEGER octetlen
char * table
#define ast_verb(level,...)
Definition: logger.h:243
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: config.c:1037
SQLSMALLINT type
static char * table
Definition: cdr_odbc.c:50
SQLSMALLINT decimals
const char * value
Definition: config.h:79
SQLINTEGER size
ODBC container.
Definition: res_odbc.h:46
char * staticvalue
#define ast_config_load(filename, flags)
Load a config file.
Definition: config.h:170
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:155
char * ast_category_browse(struct ast_config *config, const char *prev)
Goes through categories.
Definition: config.c:810
const char * name
Definition: config.h:77
char * cdrname
struct tables::odbc_columns columns
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: utils.h:663
#define LOG_ERROR
Definition: logger.h:155
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:716
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is &quot;true&quot;. This function checks to see whether a string passed to it is an indication of an &quot;true&quot; value. It checks to see if the string is &quot;yes&quot;, &quot;true&quot;, &quot;y&quot;, &quot;t&quot;, &quot;on&quot; or &quot;1&quot;.
Definition: utils.c:1533
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
#define LOG_NOTICE
Definition: logger.h:133
#define ast_free(a)
Definition: astmm.h:97
#define CONFIG
#define ast_odbc_request_obj(a, b)
Definition: res_odbc.h:123
Structure used to handle boolean flags.
Definition: utils.h:200
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:726
#define ast_calloc(a, b)
Definition: astmm.h:82
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
struct ast_variable * next
Definition: config.h:82
#define CONFIG_STATUS_FILEINVALID
Definition: config.h:52
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
Definition: res_odbc.c:1112
static int usegmtime
Definition: cdr_csv.c:52
unsigned int usegmtime
static int load_module ( void  )
static

Definition at line 742 of file cdr_adaptive_odbc.c.

References ast_cdr_register(), ast_log(), AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, ast_module_info::description, load_config(), LOG_ERROR, and odbc_log().

743 {
745  ast_log(LOG_ERROR, "Unable to lock column list. Load failed.\n");
746  return 0;
747  }
748 
749  load_config();
752  return 0;
753 }
const char * description
Definition: module.h:234
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:130
#define LOG_ERROR
Definition: logger.h:155
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
static const char name[]
static int odbc_log(struct ast_cdr *cdr)
static int load_config(void)
static int odbc_log ( struct ast_cdr cdr)
static

Definition at line 343 of file cdr_adaptive_odbc.c.

References ast_cdr::answer, ast_cdr_getvar(), ast_copy_string(), ast_free, AST_LIST_TRAVERSE, ast_localtime(), ast_log(), ast_odbc_backslash_is_escape(), ast_odbc_prepare_and_execute(), ast_odbc_release_obj(), ast_odbc_request_obj, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK, ast_str_append(), ast_str_buffer(), ast_str_create(), ast_str_set(), ast_str_strlen(), ast_strdupa, ast_strftime(), ast_strlen_zero(), ast_tvdiff_us(), ast_tvzero(), ast_verb, columns::cdrname, tables::columns, tables::connection, columns::decimals, ast_cdr::end, columns::filtervalue, first, generic_prepare(), LENGTHEN_BUF1, LENGTHEN_BUF2, LOG_ERROR, LOG_WARNING, maxsize2, columns::name, columns::octetlen, columns::radix, ast_cdr::start, columns::staticvalue, tables::table, columns::type, and tables::usegmtime.

Referenced by load_module(), and unload_module().

344 {
345  struct tables *tableptr;
346  struct columns *entry;
347  struct odbc_obj *obj;
348  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
349  char *tmp;
350  char colbuf[1024], *colptr;
351  SQLHSTMT stmt = NULL;
352  SQLLEN rows = 0;
353 
354  if (!sql || !sql2) {
355  if (sql)
356  ast_free(sql);
357  if (sql2)
358  ast_free(sql2);
359  return -1;
360  }
361 
363  ast_log(LOG_ERROR, "Unable to lock table list. Insert CDR(s) failed.\n");
364  ast_free(sql);
365  ast_free(sql2);
366  return -1;
367  }
368 
369  AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
370  int first = 1;
371  ast_str_set(&sql, 0, "INSERT INTO %s (", tableptr->table);
372  ast_str_set(&sql2, 0, " VALUES (");
373 
374  /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
375  if (!(obj = ast_odbc_request_obj(tableptr->connection, 0))) {
376  ast_log(LOG_WARNING, "cdr_adaptive_odbc: Unable to retrieve database handle for '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
377  continue;
378  }
379 
380  AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
381  int datefield = 0;
382  if (strcasecmp(entry->cdrname, "start") == 0) {
383  datefield = 1;
384  } else if (strcasecmp(entry->cdrname, "answer") == 0) {
385  datefield = 2;
386  } else if (strcasecmp(entry->cdrname, "end") == 0) {
387  datefield = 3;
388  }
389 
390  /* Check if we have a similarly named variable */
391  if (entry->staticvalue) {
392  colptr = ast_strdupa(entry->staticvalue);
393  } else if (datefield && tableptr->usegmtime) {
394  struct timeval date_tv = (datefield == 1) ? cdr->start : (datefield == 2) ? cdr->answer : cdr->end;
395  struct ast_tm tm = { 0, };
396  ast_localtime(&date_tv, &tm, "UTC");
397  ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm);
398  colptr = colbuf;
399  } else {
400  ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0, datefield ? 0 : 1);
401  }
402 
403  if (colptr) {
404  /* Check first if the column filters this entry. Note that this
405  * is very specifically NOT ast_strlen_zero(), because the filter
406  * could legitimately specify that the field is blank, which is
407  * different from the field being unspecified (NULL). */
408  if (entry->filtervalue && strcasecmp(colptr, entry->filtervalue) != 0) {
409  ast_verb(4, "CDR column '%s' with value '%s' does not match filter of"
410  " '%s'. Cancelling this CDR.\n",
411  entry->cdrname, colptr, entry->filtervalue);
412  goto early_release;
413  }
414 
415  /* Only a filter? */
416  if (ast_strlen_zero(entry->name))
417  continue;
418 
419  LENGTHEN_BUF1(strlen(entry->name));
420 
421  switch (entry->type) {
422  case SQL_CHAR:
423  case SQL_VARCHAR:
424  case SQL_LONGVARCHAR:
425 #ifdef HAVE_ODBC_WCHAR
426  case SQL_WCHAR:
427  case SQL_WVARCHAR:
428  case SQL_WLONGVARCHAR:
429 #endif
430  case SQL_BINARY:
431  case SQL_VARBINARY:
432  case SQL_LONGVARBINARY:
433  case SQL_GUID:
434  /* For these two field names, get the rendered form, instead of the raw
435  * form (but only when we're dealing with a character-based field).
436  */
437  if (strcasecmp(entry->name, "disposition") == 0) {
438  ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
439  } else if (strcasecmp(entry->name, "amaflags") == 0) {
440  ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
441  }
442 
443  /* Truncate too-long fields */
444  if (entry->type != SQL_GUID) {
445  if (strlen(colptr) > entry->octetlen) {
446  colptr[entry->octetlen] = '\0';
447  }
448  }
449 
450  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
451  LENGTHEN_BUF2(strlen(colptr));
452 
453  /* Encode value, with escaping */
454  ast_str_append(&sql2, 0, "%s'", first ? "" : ",");
455  for (tmp = colptr; *tmp; tmp++) {
456  if (*tmp == '\'') {
457  ast_str_append(&sql2, 0, "''");
458  } else if (*tmp == '\\' && ast_odbc_backslash_is_escape(obj)) {
459  ast_str_append(&sql2, 0, "\\\\");
460  } else {
461  ast_str_append(&sql2, 0, "%c", *tmp);
462  }
463  }
464  ast_str_append(&sql2, 0, "'");
465  break;
466  case SQL_TYPE_DATE:
467  if (ast_strlen_zero(colptr)) {
468  continue;
469  } else {
470  int year = 0, month = 0, day = 0;
471  if (sscanf(colptr, "%4d-%2d-%2d", &year, &month, &day) != 3 || year <= 0 ||
472  month <= 0 || month > 12 || day < 0 || day > 31 ||
473  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
474  (month == 2 && year % 400 == 0 && day > 29) ||
475  (month == 2 && year % 100 == 0 && day > 28) ||
476  (month == 2 && year % 4 == 0 && day > 29) ||
477  (month == 2 && year % 4 != 0 && day > 28)) {
478  ast_log(LOG_WARNING, "CDR variable %s is not a valid date ('%s').\n", entry->name, colptr);
479  continue;
480  }
481 
482  if (year > 0 && year < 100) {
483  year += 2000;
484  }
485 
486  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
487  LENGTHEN_BUF2(17);
488  ast_str_append(&sql2, 0, "%s{ d '%04d-%02d-%02d' }", first ? "" : ",", year, month, day);
489  }
490  break;
491  case SQL_TYPE_TIME:
492  if (ast_strlen_zero(colptr)) {
493  continue;
494  } else {
495  int hour = 0, minute = 0, second = 0;
496  int count = sscanf(colptr, "%2d:%2d:%2d", &hour, &minute, &second);
497 
498  if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
499  ast_log(LOG_WARNING, "CDR variable %s is not a valid time ('%s').\n", entry->name, colptr);
500  continue;
501  }
502 
503  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
504  LENGTHEN_BUF2(15);
505  ast_str_append(&sql2, 0, "%s{ t '%02d:%02d:%02d' }", first ? "" : ",", hour, minute, second);
506  }
507  break;
508  case SQL_TYPE_TIMESTAMP:
509  case SQL_TIMESTAMP:
510  if (ast_strlen_zero(colptr)) {
511  continue;
512  } else {
513  int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
514  int count = sscanf(colptr, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second);
515 
516  if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
517  month <= 0 || month > 12 || day < 0 || day > 31 ||
518  ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
519  (month == 2 && year % 400 == 0 && day > 29) ||
520  (month == 2 && year % 100 == 0 && day > 28) ||
521  (month == 2 && year % 4 == 0 && day > 29) ||
522  (month == 2 && year % 4 != 0 && day > 28) ||
523  hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) {
524  ast_log(LOG_WARNING, "CDR variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
525  continue;
526  }
527 
528  if (year > 0 && year < 100) {
529  year += 2000;
530  }
531 
532  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
533  LENGTHEN_BUF2(26);
534  ast_str_append(&sql2, 0, "%s{ ts '%04d-%02d-%02d %02d:%02d:%02d' }", first ? "" : ",", year, month, day, hour, minute, second);
535  }
536  break;
537  case SQL_INTEGER:
538  if (ast_strlen_zero(colptr)) {
539  continue;
540  } else {
541  int integer = 0;
542  if (sscanf(colptr, "%30d", &integer) != 1) {
543  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
544  continue;
545  }
546 
547  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
548  LENGTHEN_BUF2(12);
549  ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer);
550  }
551  break;
552  case SQL_BIGINT:
553  if (ast_strlen_zero(colptr)) {
554  continue;
555  } else {
556  long long integer = 0;
557  if (sscanf(colptr, "%30lld", &integer) != 1) {
558  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
559  continue;
560  }
561 
562  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
563  LENGTHEN_BUF2(24);
564  ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", integer);
565  }
566  break;
567  case SQL_SMALLINT:
568  if (ast_strlen_zero(colptr)) {
569  continue;
570  } else {
571  short integer = 0;
572  if (sscanf(colptr, "%30hd", &integer) != 1) {
573  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
574  continue;
575  }
576 
577  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
578  LENGTHEN_BUF2(6);
579  ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer);
580  }
581  break;
582  case SQL_TINYINT:
583  if (ast_strlen_zero(colptr)) {
584  continue;
585  } else {
586  signed char integer = 0;
587  if (sscanf(colptr, "%30hhd", &integer) != 1) {
588  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
589  continue;
590  }
591 
592  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
593  LENGTHEN_BUF2(4);
594  ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer);
595  }
596  break;
597  case SQL_BIT:
598  if (ast_strlen_zero(colptr)) {
599  continue;
600  } else {
601  signed char integer = 0;
602  if (sscanf(colptr, "%30hhd", &integer) != 1) {
603  ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
604  continue;
605  }
606  if (integer != 0)
607  integer = 1;
608 
609  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
610  LENGTHEN_BUF2(2);
611  ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", integer);
612  }
613  break;
614  case SQL_NUMERIC:
615  case SQL_DECIMAL:
616  if (ast_strlen_zero(colptr)) {
617  continue;
618  } else {
619  double number = 0.0;
620 
621  if (!strcasecmp(entry->cdrname, "billsec")) {
622  if (!ast_tvzero(cdr->answer)) {
623  snprintf(colbuf, sizeof(colbuf), "%lf",
624  (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
625  } else {
626  ast_copy_string(colbuf, "0", sizeof(colbuf));
627  }
628  } else if (!strcasecmp(entry->cdrname, "duration")) {
629  snprintf(colbuf, sizeof(colbuf), "%lf",
630  (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
631 
632  if (!ast_strlen_zero(colbuf)) {
633  colptr = colbuf;
634  }
635  }
636 
637  if (sscanf(colptr, "%30lf", &number) != 1) {
638  ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
639  continue;
640  }
641 
642  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
643  LENGTHEN_BUF2(entry->decimals);
644  ast_str_append(&sql2, 0, "%s%*.*lf", first ? "" : ",", entry->decimals, entry->radix, number);
645  }
646  break;
647  case SQL_FLOAT:
648  case SQL_REAL:
649  case SQL_DOUBLE:
650  if (ast_strlen_zero(colptr)) {
651  continue;
652  } else {
653  double number = 0.0;
654 
655  if (!strcasecmp(entry->cdrname, "billsec")) {
656  if (!ast_tvzero(cdr->answer)) {
657  snprintf(colbuf, sizeof(colbuf), "%lf",
658  (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
659  } else {
660  ast_copy_string(colbuf, "0", sizeof(colbuf));
661  }
662  } else if (!strcasecmp(entry->cdrname, "duration")) {
663  snprintf(colbuf, sizeof(colbuf), "%lf",
664  (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
665 
666  if (!ast_strlen_zero(colbuf)) {
667  colptr = colbuf;
668  }
669  }
670 
671  if (sscanf(colptr, "%30lf", &number) != 1) {
672  ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
673  continue;
674  }
675 
676  ast_str_append(&sql, 0, "%s%s", first ? "" : ",", entry->name);
677  LENGTHEN_BUF2(entry->decimals);
678  ast_str_append(&sql2, 0, "%s%lf", first ? "" : ",", number);
679  }
680  break;
681  default:
682  ast_log(LOG_WARNING, "Column type %d (field '%s:%s:%s') is unsupported at this time.\n", entry->type, tableptr->connection, tableptr->table, entry->name);
683  continue;
684  }
685  first = 0;
686  } else if (entry->filtervalue && entry->filtervalue[0] != '\0') {
687  ast_verb(4, "CDR column '%s' was not set and does not match filter of"
688  " '%s'. Cancelling this CDR.\n",
689  entry->cdrname, entry->filtervalue);
690  goto early_release;
691  }
692  }
693 
694  /* Concatenate the two constructed buffers */
696  ast_str_append(&sql, 0, ")");
697  ast_str_append(&sql2, 0, ")");
698  ast_str_append(&sql, 0, "%s", ast_str_buffer(sql2));
699 
700  ast_verb(11, "[%s]\n", ast_str_buffer(sql));
701 
703  if (stmt) {
704  SQLRowCount(stmt, &rows);
705  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
706  }
707  if (rows == 0) {
708  ast_log(LOG_WARNING, "cdr_adaptive_odbc: Insert failed on '%s:%s'. CDR failed: %s\n", tableptr->connection, tableptr->table, ast_str_buffer(sql));
709  }
710 early_release:
712  }
714 
715  /* Next time, just allocate buffers that are that big to start with. */
716  if (ast_str_strlen(sql) > maxsize) {
717  maxsize = ast_str_strlen(sql);
718  }
719  if (ast_str_strlen(sql2) > maxsize2) {
720  maxsize2 = ast_str_strlen(sql2);
721  }
722 
723  ast_free(sql);
724  ast_free(sql2);
725  return 0;
726 }
int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
Checks if the database natively supports backslash as an escape character.
Definition: res_odbc.c:1118
SQLSMALLINT radix
#define LOG_WARNING
Definition: logger.h:144
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:497
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1570
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:100
char * connection
#define LENGTHEN_BUF2(size)
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:900
struct ast_str * ast_str_create(size_t init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:420
char * filtervalue
SQLINTEGER octetlen
char * table
#define ast_verb(level,...)
Definition: logger.h:243
SQLSMALLINT type
Number structure.
Definition: app_followme.c:109
void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw)
Definition: cdr.c:264
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:874
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:77
SQLSMALLINT decimals
static int maxsize2
ODBC container.
Definition: res_odbc.h:46
char * staticvalue
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
char * cdrname
struct tables::odbc_columns columns
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: utils.h:663
struct timeval answer
Definition: cdr.h:102
#define LOG_ERROR
Definition: logger.h:155
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:364
struct sla_ringing_trunk * first
Definition: app_meetme.c:965
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
struct timeval start
Definition: cdr.h:100
#define ast_free(a)
Definition: astmm.h:97
#define ast_odbc_request_obj(a, b)
Definition: res_odbc.h:123
static int maxsize
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:2351
static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:471
struct timeval end
Definition: cdr.h:104
SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT(*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
Prepares, executes, and returns the resulting statement handle.
Definition: res_odbc.c:622
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:70
#define LENGTHEN_BUF1(size)
void ast_odbc_release_obj(struct odbc_obj *obj)
Releases an ODBC object previously allocated by ast_odbc_request_obj()
Definition: res_odbc.c:1112
unsigned int usegmtime
static int reload ( void  )
static

Definition at line 755 of file cdr_adaptive_odbc.c.

References ast_log(), AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, free_config(), load_config(), and LOG_ERROR.

756 {
758  ast_log(LOG_ERROR, "Unable to lock column list. Reload failed.\n");
759  return -1;
760  }
761 
762  free_config();
763  load_config();
765  return 0;
766 }
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
#define LOG_ERROR
Definition: logger.h:155
static int free_config(void)
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
static int load_config(void)
static int unload_module ( void  )
static

Definition at line 728 of file cdr_adaptive_odbc.c.

References ast_cdr_register(), ast_cdr_unregister(), ast_log(), AST_RWLIST_UNLOCK, AST_RWLIST_WRLOCK, ast_module_info::description, free_config(), LOG_ERROR, and odbc_log().

729 {
733  ast_log(LOG_ERROR, "Unable to lock column list. Unload failed.\n");
734  return -1;
735  }
736 
737  free_config();
739  return 0;
740 }
const char * description
Definition: module.h:234
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:130
#define LOG_ERROR
Definition: logger.h:155
static int free_config(void)
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
static const char name[]
static int odbc_log(struct ast_cdr *cdr)
void ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:165

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Adaptive ODBC CDR backend" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "ac1f6a56484a8820659555499174e588" , .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CDR_DRIVER, }
static

Definition at line 773 of file cdr_adaptive_odbc.c.

Definition at line 773 of file cdr_adaptive_odbc.c.

int maxsize = 512
static

Definition at line 55 of file cdr_adaptive_odbc.c.

Referenced by ast_func_read2().

int maxsize2 = 512
static

Definition at line 55 of file cdr_adaptive_odbc.c.

Referenced by odbc_log().

const char name[] = "Adaptive ODBC"
static

Definition at line 53 of file cdr_adaptive_odbc.c.

Referenced by __analog_ss_thread(), __ast_change_name_nolink(), __ast_channel_alloc_ap(), __dahdi_exception(), __iax2_show_peers(), _sip_show_peers_one(), acf_curl_helper(), adsi_load(), adsi_message(), advanced_options(), aelsub_exec(), aji_cli_create_collection(), aji_cli_create_leafnode(), aji_cli_delete_pubsub_node(), aji_cli_list_pubsub_nodes(), aji_cli_purge_pubsub_nodes(), alias_name_cb(), analog_exception(), analog_ss_thread(), aoc_amount_str(), append_var_and_value_to_filter(), ast_cc_monitor_count(), ast_cel_fabricate_channel_from_event(), ast_channel_get_full(), ast_connected_line_source_parse(), ast_dsp_set_call_progress_zone(), ast_event_str_to_ie_type(), ast_getformatname_multiple(), ast_jb_read_conf(), ast_module_helper(), ast_monitor_change_fname(), ast_monitor_start(), ast_parse_caller_presentation(), ast_party_name_charset_parse(), ast_redirecting_reason_parse(), ast_register_thread(), ast_rtp_lookup_mime_multiple2(), ast_setstate(), ast_str2tos(), ast_syslog_facility(), ast_syslog_priority(), ast_taskprocessor_get(), AST_TEST_DEFINE(), ast_var_name(), ast_waitfor_nandfds(), build_calendar(), build_peer(), build_user(), callerid_read(), change_monitor_action(), channel_iterator_search(), channel_spy(), check_user_full(), cli_tps_ping(), cli_tps_report(), complete_trans_path_choice(), config_ldap(), count_agents_cb(), do_pause_or_unpause(), dump_ies(), dump_prov_ies(), entry_cmp_fn(), fac2str(), fax_session_tab_complete(), find_calendar(), find_context(), find_macro(), find_peer(), find_pvt(), find_user(), findparkinglotname(), get_esc(), group_cmp_fn(), gtalk_ringing_ack(), handle_cli_core_show_translation(), handle_cli_osp_show(), handle_cli_status(), handle_redirect(), handle_register_message(), handle_showchan(), handle_tcptls_connection(), httpd_helper_thread(), load_module(), lua_get_variable(), lua_get_variable_value(), lua_set_variable(), lua_set_variable_value(), manager_mute_mixmonitor(), map_video_codec(), match_agent(), misdn_cfg_get_config_string(), misdn_cfg_get_name(), mwi_thread(), my_get_callerid(), oss_call(), oss_request(), parse_cookies(), party_id_write(), peek_read(), phone_request(), play_message_callerid(), process_echocancel(), process_opcode(), process_returncode(), proxy_from_config(), pvalAppCallSetAppName(), pvalCatchSetExtName(), pvalContextSetName(), pvalESwitchesAddSwitch(), pvalExtenSetName(), pvalLabelSetName(), pvalMacroCallSetMacroName(), pvalMacroSetName(), pvalSwitchesAddSwitch(), pvalVarDecSetVarname(), realtime_peer_get_sippeer_helper(), register_verify(), reload_module(), serialize_showchan(), set_hangup_source_and_cause(), show_config_description(), sip_acf_channel_read(), sip_prepare_socket(), sip_prune_realtime(), sip_queue_hangup_cause(), sla_add_trunk_to_station(), sla_find_station(), sla_find_trunk(), softhangup_exec(), start_monitor_action(), stop_monitor_action(), tps_taskprocessor_tab_complete(), unistim_new(), unload_module(), and update_call_counter().

struct odbc_tables odbc_tables = { .first = NULL, .last = NULL, .lock = { PTHREAD_RWLOCK_INITIALIZER , NULL, 1 } , }
static