Asterisk - The Open Source Telephony Project GIT-master-80b953f
Loading...
Searching...
No Matches
res/cdrel_custom/config.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2026, Sangoma Technologies Corporation
5 *
6 * George Joseph <gjoseph@sangoma.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*!
20 * \file
21 * \author George Joseph <gjoseph@sangoma.com>
22 *
23 * \brief Common config file handling for res_cdrel_custom.
24 *
25 * This file is a 'bit' complex. The reasoning is that the functions
26 * do as much work as possible at module load time to reduce the workload
27 * at run time.
28 *
29 */
30
31#include "cdrel.h"
32
33#include "asterisk/config.h"
34#include "asterisk/module.h"
35#include "asterisk/paths.h"
36
37/*
38 * The DSV files get placed in specific subdirectories
39 * while the SQL databases get placed directly in /var/log/asterisk.
40 */
43 [cdrel_record_cdr] = "cdr-custom",
44 [cdrel_record_cel] = "cel-custom",
45 },
49 }
50};
51
61
62/*
63 * To maximize the possibility that we can put a legacy config through the
64 * much faster advanced process, we need to ensure that we can handle
65 * everything in the legacy config.
66 */
67static const char *allowed_functions[] = {
68 [cdrel_record_cdr] = "CSV_QUOTE CDR CALLERID CHANNEL",
69 [cdrel_record_cel] = "CSV_QUOTE CALLERID CHANNEL eventtype eventtime eventenum userdeftype eventextra BRIDGEPEER",
70};
71
72static const char *special_vars[] = {
73 [cdrel_record_cdr] = "",
74 [cdrel_record_cel] = "eventtype eventtime eventenum userdeftype eventextra BRIDGEPEER",
75};
76
77/*!
78 * \internal
79 * \brief Parse a raw legacy field template.
80 *
81 * \example
82 *
83 * ${CSV_QUOTE(${eventtype})}
84 * ${CSV_QUOTE(${CALLERID(name)})}
85 * ${CSV_QUOTE(${CDR(src)})}
86 * ${CDR(uservar)}
87 * "some literal"
88 * ${CSV_QUOTE("some literal")}
89 *
90 * \param record_type CDR or CEL
91 * \param input_field_template The trimmed raw field template
92 * \return
93 */
94
95static struct field_parse_result parse_field(enum cdrel_record_type record_type, char *input_field_template)
96{
97 char *tmp_field = NULL;
98 struct field_parse_result result = { 0, };
99
100 /*
101 * If the template starts with a double-quote, it's automatically
102 * a literal.
103 */
104 if (input_field_template[0] == '"') {
105 result.result = ast_strdup(ast_strip_quoted(ast_strdupa(input_field_template), "\"", "\""));
106 result.csv_quote = 1;
107 result.is_literal = 1;
108 return result;
109 }
110
111 /*
112 * If it starts with a single quote, it's probably a legacy SQL template
113 * so we need to force quote it on output.
114 */
115 tmp_field = ast_strip(ast_strdupa(input_field_template));
116
117 if (tmp_field[0] == '\'') {
118 result.csv_quote = 1;
119 }
120
121 /*
122 * I really hate the fact that ast_strip really trims whitespace
123 * and ast_strip_quoted will strip anything in pairs.
124 * Anyway, get rid of any remaining enclosing quotes.
125 */
126 tmp_field = ast_strip(ast_strip_quoted(tmp_field, "\"'", "\"'"));
127
128 /*
129 * If the template now starts with a '$' it's either a dialplan function
130 * call or one of the special CEL field names.
131 *
132 * Examples: ${CSV_QUOTE(${CALLERID(name)})}
133 * ${eventtime}
134 * We're going to iterate over function removal until there's just
135 * a plain text string left.
136 *
137 */
138 while (tmp_field[0] == '$') {
139 char *ptr = NULL;
140 /*
141 * A function name longer that 64 characters is highly unlikely but
142 * we'll check later.
143 */
144 char func_name[65];
145
146 /*
147 * Skip over the '$'
148 * {CSV_QUOTE(${CALLERID(name)})}
149 * {eventtime}
150 */
151 tmp_field++;
152 /*
153 * Remove any enclosing brace-like characters
154 * CSV_QUOTE(${CALLERID(name)})
155 * eventtime
156 */
157 tmp_field = ast_strip(ast_strip_quoted(tmp_field, "[{(", "]})"));
158
159 /*
160 * Check what's left to see if it matches a special variable.
161 * If it does (like "eventtime" in the example), we're done.
162 */
163 if (strstr(special_vars[record_type], tmp_field) != NULL) {
164 result.functions++;
165 break;
166 }
167
168 /*
169 * At this point, it has to be a function name so find the
170 * openening '('.
171 * CSV_QUOTE(${CALLERID(name)})
172 * ^
173 * If we don't find one, it's something we don't recognise
174 * so bail.
175 */
176 ptr = strchr(tmp_field, '(');
177 if (!ptr) {
178 result.parse_failed++;
179 continue;
180 }
181
182 /*
183 * Copy from the beginning to the '(' to func_name,
184 * not exceeding func_name's size.
185 *
186 * CSV_QUOTE(${CALLERID(name)})
187 * ^
188 * CSV_QUOTE
189 *
190 * Then check that it's a function we can handle.
191 * If not, bail.
192 */
193 ast_copy_string(func_name, tmp_field, MIN(sizeof(func_name), ptr - tmp_field + 1));
194 if (strstr(allowed_functions[record_type], func_name) == NULL) {
195 result.parse_failed++;
196 result.unknown_functions++;
197 continue;
198 }
199 result.functions++;
200 /*
201 * If the function is CSV_QUOTE, we need to set the csv_quote flag.
202 */
203 if (strcmp("CSV_QUOTE", func_name) == 0) {
204 result.csv_quote = 1;
205 } else if (strcmp("CDR", func_name) == 0) {
206 result.cdr = 1;
207 }
208
209 /*
210 * ptr still points to the opening '(' so now strip it and the
211 * matching parens.
212 *
213 * ${CALLERID(name)}
214 *
215 */
216 tmp_field = ast_strip_quoted(ptr, "(", ")");
217 if (tmp_field[0] == '"' || tmp_field[0] == '\'') {
218 result.result = ast_strdup(ast_strip_quoted(tmp_field, "\"'", "\"'"));
219 result.csv_quote = 1;
220 result.is_literal = 1;
221 return result;
222 }
223
224 /* Repeat the loop until there are no more functions or variables */
225 }
226
227 /*
228 * If the parse failed we'll send back the entire template.
229 */
230 if (result.parse_failed) {
231 tmp_field = input_field_template;
232 } else {
233 /*
234 * If there were no functions or variables parsed then we'll
235 * assume it's a literal.
236 */
237 if (result.functions == 0) {
238 result.is_literal = 1;
239 }
240 }
241
242 result.result = ast_strdup(tmp_field);
243 if (result.result == NULL) {
244 result.parse_failed = 1;
245 }
246
247 return result;
248}
249
250/*!
251 * \internal
252 * \brief Parse a legacy DSV template string into a vector of individual strings.
253 *
254 * The resulting vector will look like it came from an advanced config and will
255 * be treated as such.
256 *
257 * \param record_type CDR or CEL.
258 * \param config_filename Config filename for logging purposes.
259 * \param output_filename Output filename for logging purposes.
260 * \param template The full template.
261 * \param fields A pointer to a string vector to receive the result.
262 * \retval 1 on success.
263 * \retval 0 on failure.
264 */
265static int parse_legacy_template(enum cdrel_record_type record_type, const char *config_filename,
266 const char *output_filename, const char *input_template, struct ast_vector_string *fields)
267{
268 char *template = ast_strdupa(input_template);
269 char *field_template = NULL;
270 int res = 0;
271
272 /*
273 * We have no choice but to assume that a legacy config template uses commas
274 * as field delimiters. We don't have a reliable way to determine this ourselves.
275 */
276 while((field_template = ast_strsep(&template, ',', AST_STRSEP_TRIM))) {
277 char *uservar = "";
278 char *literal = "";
279 /* Try to parse the field. */
280 struct field_parse_result result = parse_field(record_type, field_template);
281
282 ast_debug(2, "field: '%s' literal: %d quote: %d cdr: %d failed: %d funcs: %d unknfuncs: %d\n", result.result,
283 result.is_literal, result.csv_quote, result.cdr,
284 result.parse_failed, result.functions, result.unknown_functions);
285
286 /*
287 * If it failed,
288 */
289 if (!result.result || result.parse_failed) {
290 ast_free(result.result);
291 return 0;
292 }
293 if (result.is_literal) {
294 literal = "literal^";
295 }
296
297 if (!get_registered_field_by_name(record_type, result.result)) {
298 ast_debug(3, " %s->%s: field '%s' not found\n", cdrel_basename(config_filename),
299 cdrel_basename(output_filename), result.result);
300 /*
301 * If the result was found in a CDR function, treat it as a CDR user variable
302 * otherwise treat it as a literal.
303 */
304 if (result.cdr) {
305 uservar = "uservar^";
306 } else {
307 literal = "literal^";
308 }
309 }
310 res = ast_asprintf(&field_template, "%s(%s%s)", result.result, S_OR(literal,uservar), result.csv_quote ? "quote" : "noquote");
311 ast_free(result.result);
312
313 if (!field_template || res < 0) {
314 ast_free(field_template);
315 return 0;
316 }
317 res = AST_VECTOR_APPEND(fields, field_template);
318 if (res != 0) {
319 ast_free(field_template);
320 return 0;
321 }
322 ast_debug(2, " field template: %s\n", field_template);
323 }
324
325 return 1;
326}
327
328/*!
329 * \fn Parse an advanced field template and allocate a cdrel_field for it.
330 * \brief
331 *
332 * \param config Config object.
333 * \param input_field_template Trimmed advanced field template.
334 * \return
335 */
336static struct cdrel_field *field_alloc(struct cdrel_config *config, const char *input_field_template)
337{
338 RAII_VAR(struct cdrel_field *, field, NULL, ast_free);
339 const struct cdrel_field *registered_field = NULL;
340 struct cdrel_field *rtn_field = NULL;
341 char *field_name = NULL;
342 char *data = NULL;
343 char *tmp_data = NULL;
344 char *closeparen = NULL;
345 char *qualifier = NULL;
346 enum cdrel_data_type forced_output_data_type = cdrel_data_type_end;
347 struct ast_flags field_flags = { 0 };
348
349 /*
350 * The database fields are specified field-by-field for legacy so we treat them
351 * as literals containing expressions which will be evaluated record-by-record.
352 */
353 if (config->backend_type == cdrel_backend_db && config->config_type == cdrel_config_legacy) {
354 registered_field = get_registered_field_by_name(config->record_type, "literal");
355 ast_assert(registered_field != NULL);
356 rtn_field = ast_calloc(1, sizeof(*field) + strlen(input_field_template) + 1);
357 if (!rtn_field) {
358 return NULL;
359 }
360 memcpy(rtn_field, registered_field, sizeof(*registered_field));
361 strcpy(rtn_field->data, input_field_template); /* Safe */
362 return rtn_field;
363 }
364
365 /*
366 * If the field template is a quoted string, it's a literal.
367 * We don't check for qualifiers.
368 */
369 if (input_field_template[0] == '"' || input_field_template[0] == '\'') {
370 data = ast_strip_quoted(ast_strdupa(input_field_template), "\"'", "\"'");
371 ast_set_flag(&field_flags, cdrel_flag_literal);
372 ast_debug(3, " Using qualifier 'literal' for field '%s' flags: %s\n", data,
373 ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
374 field_name = "literal";
375 } else {
376 field_name = ast_strdupa(input_field_template);
377 data = strchr(field_name, '(');
378
379 if (data) {
380 *data = '\0';
381 data++;
382 closeparen = strchr(data, ')');
383 if (closeparen) {
384 *closeparen = '\0';
385 }
386 }
387 }
388
389 if (!ast_strlen_zero(data) && !ast_test_flag(&field_flags, cdrel_flag_literal)) {
390 char *data_swap = NULL;
391 tmp_data = ast_strdupa(data);
392
393 while((qualifier = ast_strsep(&tmp_data, '^', AST_STRSEP_STRIP | AST_STRSEP_TRIM))) {
395 if (ast_strlen_zero(qualifier)) {
396 continue;
397 }
398 fodt = cdrel_data_type_from_str(qualifier);
399 if (fodt < cdrel_data_type_end) {
401 if (fodt == cdrel_type_uservar) {
402 ast_set_flag(&field_flags, cdrel_flag_uservar);
403 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
404 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
405 data_swap = ast_strdupa(field_name);
406 field_name = "uservar";
407 } else if (fodt == cdrel_type_literal) {
408 ast_set_flag(&field_flags, cdrel_flag_literal);
409 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
410 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
411 data_swap = ast_strdupa(field_name);
412 field_name = "literal";
413 } else {
414 forced_output_data_type = fodt;
415 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
416 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
417 }
418 continue;
419 }
420 if (strcasecmp(qualifier, "quote") == 0) {
421 ast_set_flag(&field_flags, cdrel_flag_quote);
422 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
423 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
424 continue;
425 }
426 if (strcasecmp(qualifier, "noquote") == 0) {
427 ast_set_flag(&field_flags, cdrel_flag_noquote);
428 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
429 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
430 continue;
431 }
432 if (strchr(qualifier, '%') != NULL) {
433 data_swap = ast_strdupa(qualifier);
435 forced_output_data_type = cdrel_type_string;
436 ast_debug(3, " Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
437 field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
438 }
439 }
440 if (ast_test_flag(&field_flags, cdrel_flag_quote) && ast_test_flag(&field_flags, cdrel_flag_noquote)) {
441 ast_log(LOG_WARNING, "%s->%s: Field '%s(%s)' has both quote and noquote\n",
442 config->config_filename, config->output_filename, field_name, data);
443 return NULL;
444 }
445 data = data_swap;
446 }
447
448 /*
449 * Check again for literal.
450 */
451 if (ast_test_flag(&field_flags, cdrel_flag_literal)) {
452 if (config->format_type == cdrel_format_json && !strchr(data, ':')) {
453 ast_log(LOG_WARNING, "%s->%s: Literal field '%s' must be formatted as \"name: value\" when using the 'json' format\n",
454 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
455 input_field_template);
456 return NULL;
457 }
458 }
459
460 /*
461 * Now look the field up by just the field name without any data.
462 */
463 registered_field = get_registered_field_by_name(config->record_type, field_name);
464 if (!registered_field) {
465 ast_log(LOG_WARNING, "%s->%s: Field '%s' not found\n",
466 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename), field_name);
467 return NULL;
468 }
469
470 if (ast_test_flag(&field_flags, cdrel_flag_format_spec)
471 && registered_field->input_data_type != cdrel_type_timeval) {
472 ast_log(LOG_WARNING, "%s->%s: Custom format '%s' ignored for field '%s'."
473 " Only timeval types can use custom format strings.\n",
474 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
475 data, field_name);
476 forced_output_data_type = cdrel_data_type_end;
478 data = NULL;
479 }
480
481 field = ast_calloc(1, sizeof(*registered_field) + strlen(input_field_template) + 1);
482 if (!field) {
483 return NULL;
484 }
485 memcpy(field, registered_field, sizeof(*field));
486
487 if (!ast_strlen_zero(data)) {
488 strcpy(field->data, data); /* Safe */
489 }
490
491 /*
492 * For user variables, we use the field name from the data
493 * we set above.
494 */
495 if (field->input_data_type == cdrel_type_uservar) {
496 field->name = field->data;
497 }
498
499 if (field->input_data_type == cdrel_type_literal && config->format_type == cdrel_format_json) {
500 /*
501 * data should look something like this... lname: lvalue
502 * We'll need to make field->name point to "lname" and
503 * field->data point to "lvalue" so that when output the
504 * json will look like... "lname": "lvalue".
505 * Since field->data is already long enough to to handle both,
506 * we'll do this...
507 * field->data = lvalue\0lname\0
508 * field->name = ^
509 */
510 char *ptr = strchr(data, ':');/* Safe since we checked data for a ':' above */
511 *ptr = '\0';
512 ptr++;
513 /*
514 * data: lname\0 lvalue
515 * ptr: ^
516 */
517 strcpy(field->data, ast_strip_quoted(ptr, "\"", "\"")); /* Safe */
518 /*
519 * field->data: lvalue\0
520 */
521 ptr = field->data + strlen(field->data);
522 ptr++;
523 /*
524 * field->data: lvalue\0
525 * ptr: ^
526 * data: lname\0 lvalue
527 */
528 strcpy(ptr, data); /* Safe */
529 /*
530 * field->data: lvalue\0lname\0
531 */
532 field->name = ptr;
533 /*
534 * field->data: lvalue\0lname\0
535 * field->name: ^
536 */
537 }
538
539 if (forced_output_data_type < cdrel_data_type_end) {
540 field->output_data_type = forced_output_data_type;
541 }
542 field->flags = field_flags;
543
544 /*
545 * Unless the field has the 'noquote' flag, we'll set the 'quote'
546 * flag if quoting method is 'all' or 'non_numeric'.
547 */
548 if (!ast_test_flag(&field->flags, cdrel_flag_noquote)) {
549 if (config->quoting_method == cdrel_quoting_method_all) {
550 ast_set_flag(&field->flags, cdrel_flag_quote);
551 } else if (config->quoting_method == cdrel_quoting_method_non_numeric) {
552 if (field->output_data_type > cdrel_data_type_strings_end) {
553 ast_set_flag(&field->flags, cdrel_flag_noquote);
554 } else {
555 ast_set_flag(&field->flags, cdrel_flag_quote);
556 }
557 }
558 }
559
560 if (config->quoting_method == cdrel_quoting_method_none) {
561 ast_clear_flag(&field->flags, cdrel_flag_quote);
562 ast_set_flag(&field->flags, cdrel_flag_noquote);
563 }
564
565 ast_debug(2, "%s->%s: Field '%s' processed -> name:'%s' input_type:%s output_type:%s flags:'%s' data:'%s'\n",
566 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename), input_field_template,
567 field->name, DATA_TYPE_STR(field->input_data_type),
568 DATA_TYPE_STR(field->output_data_type),
569 ast_str_tmp(128, cdrel_get_field_flags(&field->flags, &STR_TMP)),
570 field->data);
571
572 rtn_field = field;
573 field = NULL;
574 return rtn_field;
575}
576
578 AST_VECTOR_RESET(fields, ast_free);
579 AST_VECTOR_PTR_FREE(fields);
580}
581
582/*!
583 * \internal
584 * \brief Load all the fields in the string vector.
585 *
586 * \param config Config object
587 * \param fields String vector.
588 * \retval 0 on success.
589 * \retval -1 on failure.
590 */
591static int load_fields(struct cdrel_config *config, struct ast_vector_string *fields)
592{
593 int res = 0;
594 int ix = 0;
595
596 ast_debug(1, "%s->%s: Loading fields\n", cdrel_basename(config->config_filename),
597 cdrel_basename(config->output_filename));
598
599 for (ix = 0; ix < AST_VECTOR_SIZE(fields); ix++) {
600 char *field_name = AST_VECTOR_GET(fields, ix);
601 struct cdrel_field *field = NULL;
602
603 field = field_alloc(config, field_name);
604 if (!field) {
605 res = -1;
606 continue;
607 }
608
609 if (AST_VECTOR_APPEND(&config->fields, field) != 0) {
610 ast_free(field);
611 return -1;
612 }
613 }
614
615 return res;
616}
617
618static void config_free(struct cdrel_config *config)
619{
620 if (!config) {
621 return;
622 }
623
624 if (config->insert) {
625 sqlite3_finalize(config->insert);
626 config->insert = NULL;
627 }
628
629 if (config->db) {
630 sqlite3_close(config->db);
631 config->db = NULL;
632 }
633
637 AST_VECTOR_FREE(&config->fields);
639}
640
641/*!
642 * \internal
643 * \brief Allocate a config object.
644 *
645 * You should know what these are by now :)
646 *
647 * \param record_type
648 * \param backend_type
649 * \param config_type
650 * \param config_filename
651 * \param output_filename
652 * \param template
653 * \return
654 */
655static struct cdrel_config *config_alloc(enum cdrel_record_type record_type,
656 enum cdrel_backend_type backend_type, enum cdrel_config_type config_type,
657 const char *config_filename, const char *output_filename, const char *template)
658{
660 struct cdrel_config *rtn_config = NULL;
661 const char *file_suffix = "";
662 int res = 0;
663
664 ast_debug(1, "%s->%s: Loading\n", cdrel_basename(config_filename), cdrel_basename(output_filename));
665
667 if (!config) {
668 return NULL;
669 }
670
672 return NULL;
673 }
674
675 config->record_type = record_type;
676 config->backend_type = backend_type;
677 config->dummy_channel_alloc = cdrel_dummy_channel_allocators[record_type];
678 config->config_type = config_type;
679
680 /* Set defaults */
681 config->format_type = cdrel_format_dsv;
682 config->separator[0] = ',';
683 switch(backend_type) {
685 config->quote[0] = '"';
686 config->quoting_method = cdrel_quoting_method_all;
687 break;
688 case cdrel_backend_db:
689 config->quote[0] = '\0';
690 config->format_type = cdrel_format_sql;
691 config->quoting_method = cdrel_quoting_method_none;
692 if (!ast_ends_with(output_filename, ".db")) {
693 file_suffix = ".db";
694 }
695 break;
696 default:
697 ast_log(LOG_ERROR, "%s->%s: Unknown backend type '%d'\n", cdrel_basename(config_filename),
698 cdrel_basename(output_filename), backend_type);
699 break;
700 }
701 config->quote_escape[0] = config->quote[0];
702
703 res = ast_string_field_set(config, template, template);
704 if (res != 0) {
705 return NULL;
706 }
707
708 if (output_filename[0] == '/') {
709 res = ast_string_field_build(config, output_filename, "%s%s", output_filename, file_suffix);
710 } else {
711 const char *subdir = dirname_map[backend_type][record_type];
712 res = ast_string_field_build(config, output_filename, "%s/%s%s%s%s",
713 ast_config_AST_LOG_DIR, S_OR(subdir, ""), ast_strlen_zero(subdir) ? "" : "/", output_filename, file_suffix);
714 }
715 if (res != 0) {
716 return NULL;
717 }
718 ast_mutex_init(&config->lock);
719
720 rtn_config = config;
721 config = NULL;
722 return rtn_config;
723}
724
725/*!
726 * \internal
727 * \brief Load the "columns" parameter from a database config.
728 *
729 * \param config Config object
730 * \param columns The columns parameter.
731 * \param column_count Set to the count of columns parsed.
732 * \retval 0 on success.
733 * \retval -1 on failure.
734 */
735static int load_database_columns(struct cdrel_config *config, const char *columns, int *column_count)
736{
737 char *col = NULL;
738 char *cols = NULL;
739 RAII_VAR(struct ast_str *, column_string, NULL, ast_free);
740
741 ast_debug(1, "%s->%s: Loading columns\n", cdrel_basename(config->config_filename),
742 cdrel_basename(config->output_filename));
743
744 if (!(column_string = ast_str_create(1024))) {
745 return -1;
746 }
747
748 cols = ast_strdupa(columns);
749 *column_count = 0;
750
751 /* We need to trim and remove any single or double quotes from each column name. */
752 while((col = ast_strsep(&cols, ',', AST_STRSEP_TRIM))) {
753 col = ast_strip(ast_strip_quoted(col, "'\"", "'\""));
754 if (ast_str_append(&column_string, 0, "%s%s", *column_count ? "," : "", col) <= 0) {
755 return -1;
756 }
757 (*column_count)++;
758 }
759
760 if (ast_string_field_set(config, db_columns, ast_str_buffer(column_string)) != 0) {
761 return -1;
762 }
763
764 return 0;
765}
766
768{
769 char *placeholders = ast_malloc(2 * columns), *c = placeholders;
770 if (placeholders) {
771 for (; columns; columns--) {
772 *c++ = '?';
773 *c++ = ',';
774 }
775 *(c - 1) = 0;
776 }
777 return placeholders;
778}
779
780/*!
781 * \internal
782 * \brief Open an sqlite3 database and create the table if needed.
783 *
784 * \param config Config object.
785 * \retval 0 on success.
786 * \retval -1 on failure.
787 */
789{
790 char *sql = NULL;
791 int res = 0;
792 char *placeholders = NULL;
793
794 ast_debug(1, "%s->%s: opening database\n", cdrel_basename(config->config_filename),
795 cdrel_basename(config->output_filename));
796 res = sqlite3_open(config->output_filename, &config->db);
797 if (res != SQLITE_OK) {
798 ast_log(LOG_WARNING, "%s->%s: Could not open database\n", cdrel_basename(config->config_filename),
799 cdrel_basename(config->output_filename));
800 return -1;
801 }
802
803 sqlite3_busy_timeout(config->db, config->busy_timeout);
804
805 /* is the table there? */
806 sql = sqlite3_mprintf("SELECT COUNT(*) FROM %q;", config->db_table);
807 if (!sql) {
808 return -1;
809 }
810 res = sqlite3_exec(config->db, sql, NULL, NULL, NULL);
811 sqlite3_free(sql);
812 if (res != SQLITE_OK) {
813 /*
814 * Create the table.
815 * We don't use %q for the column list here since we already escaped when building it
816 */
817 sql = sqlite3_mprintf("CREATE TABLE %q (AcctId INTEGER PRIMARY KEY, %s)",
818 config->db_table, config->db_columns);
819 res = sqlite3_exec(config->db, sql, NULL, NULL, NULL);
820 sqlite3_free(sql);
821 if (res != SQLITE_OK) {
822 ast_log(LOG_WARNING, "%s->%s: Unable to create table '%s': %s\n",
823 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
824 config->db_table, sqlite3_errmsg(config->db));
825 return -1;
826 }
827 } else {
828 /*
829 * If the table exists, make sure the number of columns
830 * matches the config.
831 */
832 sqlite3_stmt *get_stmt;
833 int existing_columns = 0;
834 int config_columns = AST_VECTOR_SIZE(&config->fields);
835
836 sql = sqlite3_mprintf("SELECT * FROM %q;", config->db_table);
837 if (!sql) {
838 return -1;
839 }
840 res = sqlite3_prepare_v2(config->db, sql, -1, &get_stmt, NULL);
841 sqlite3_free(sql);
842 if (res != SQLITE_OK) {
843 ast_log(LOG_WARNING, "%s->%s: Unable to get column count for table '%s': %s\n",
844 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
845 config->db_table, sqlite3_errmsg(config->db));
846 return -1;
847 }
848 /*
849 * prepare figures out the number of columns that would be in a result
850 * set. We don't need to execute the statement.
851 */
852 existing_columns = sqlite3_column_count(get_stmt);
853 sqlite3_finalize(get_stmt);
854 /* config_columns doesn't include the sequence field */
855 if ((config_columns + 1) != existing_columns) {
856 ast_log(LOG_WARNING, "%s->%s: The number of fields in the config (%d) doesn't equal the"
857 " nummber of data columns (%d) in the existing %s table. This config is disabled.\n",
858 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
859 config_columns, existing_columns - 1, config->db_table);
860 return -1;
861 }
862 }
863
864 placeholders = make_stmt_placeholders(AST_VECTOR_SIZE(&config->fields));
865 if (!placeholders) {
866 return -1;
867 }
868
869 /* Inserting NULL in the ID column still generates an ID */
870 sql = sqlite3_mprintf("INSERT INTO %q VALUES (NULL,%s)", config->db_table, placeholders);
871 ast_free(placeholders);
872 if (!sql) {
873 return -1;
874 }
875
876 res = sqlite3_prepare_v3(config->db, sql, -1, SQLITE_PREPARE_PERSISTENT, &config->insert, NULL);
877 if (res != SQLITE_OK) {
878 ast_log(LOG_ERROR, "%s->%s: Unable to prepare INSERT statement '%s': %s\n",
879 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
880 sql, sqlite3_errmsg(config->db));
881 return -1;
882 }
883
884 return 0;
885}
886
887/*!
888 * \internal
889 * \brief Load a database config from a config file category.
890 *
891 * \param record_type CDR or CEL.
892 * \param category The category (becomes the database file name).
893 * \param config_filename The config filename for logging purposes.
894 * \return config or NULL.
895 */
897 struct ast_category *category, const char *config_filename)
898{
899 const char *category_name = ast_category_get_name(category);
901 struct cdrel_config *rtn_config = NULL;
902 int res = 0;
903 int column_count = 0;
904 int value_count = 0;
905 int field_check_passed = 0;
906 const char *template = ast_variable_find(category, "values");
907 enum cdrel_config_type config_type;
908 const char *value;
909 char *tmp_fields = NULL;
910 RAII_VAR(struct ast_vector_string *, field_templates, ast_calloc(1, sizeof(*field_templates)), field_template_vector_free);
911
912 if (!ast_strlen_zero(template)) {
913 config_type = cdrel_config_legacy;
914 } else {
915 template = ast_variable_find(category, "fields");
916 if (!ast_strlen_zero(template)) {
917 config_type = cdrel_config_advanced;
918 }
919 }
920 if (ast_strlen_zero(template)) {
921 ast_log(LOG_WARNING, "%s->%s: Neither 'values' nor 'fields' specified\n",
923 return NULL;
924 }
925
926 res = AST_VECTOR_INIT(field_templates, 25);
927 if (res != 0) {
928 return NULL;
929 }
930
931 /*
932 * Let's try and and parse a legacy config to see if we can turn
933 * it into an advanced condig.
934 */
935 if (config_type == cdrel_config_legacy) {
936 field_check_passed = parse_legacy_template(record_type, config_filename,
937 category_name, template, field_templates);
938 if (field_check_passed) {
939 config_type = cdrel_config_advanced;
940 ast_log(LOG_NOTICE, "%s->%s: Legacy config upgraded to advanced\n",
942 } else {
943 AST_VECTOR_RESET(field_templates, ast_free);
944 ast_log(LOG_NOTICE, "%s->%s: Unable to upgrade legacy config to advanced. Continuing to process as legacy\n",
946 }
947 }
948
949 /*
950 * If we could, the fields vector will be populated so we don't need to do it again.
951 * If it was an advanced config or a legacy one we couldn't parse,
952 * we need to split the string into the vector.
953 */
954 if (AST_VECTOR_SIZE(field_templates) == 0) {
955 tmp_fields = ast_strdupa(template);
956 while((value = ast_strsep(&tmp_fields, ',', AST_STRSEP_TRIM))) {
957 res = AST_VECTOR_APPEND(field_templates, ast_strdup(value));
958 if (res != 0) {
959 return NULL;
960 }
961 }
962 }
963
964 config = config_alloc(record_type, cdrel_backend_db, config_type,
965 config_filename, category_name, template);
966 if (!config) {
967 return NULL;
968 }
969
970 config->busy_timeout = 1000;
971
972 res = ast_string_field_set(config, db_table,
973 S_OR(ast_variable_find(category, "table"), config->record_type == cdrel_record_cdr ? "cdr" : "cel"));
974 if (res != 0) {
975 return NULL;
976 }
977
978 /* sqlite3_busy_timeout in miliseconds */
979 if ((value = ast_variable_find(category, "busy_timeout")) != NULL) {
980 if (ast_parse_arg(value, PARSE_INT32|PARSE_DEFAULT, &config->busy_timeout, 1000) != 0) {
981 ast_log(LOG_WARNING, "%s->%s: Invalid busy_timeout value '%s' specified. Using 1000 instead.\n",
982 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename), value);
983 }
984 }
985
986 /* Columns */
987 value = ast_variable_find(category, "columns");
988 if (ast_strlen_zero(value)) {
989 ast_log(LOG_WARNING, "%s->%s: The 'columns' parameter is missing",
990 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename));
991 return NULL;
992 }
993
994 if (load_database_columns(config, value, &column_count) != 0) {
995 return NULL;
996 }
997
998 if (AST_VECTOR_INIT(&config->fields, AST_VECTOR_SIZE(field_templates)) != 0) {
999 return NULL;
1000 }
1001
1002 if (load_fields(config, field_templates) != 0) {
1003 return NULL;
1004 }
1005
1006 value_count = AST_VECTOR_SIZE(&config->fields);
1007
1008 if (value_count != column_count) {
1009 ast_log(LOG_WARNING, "%s->%s: There are %d columns but %d values\n",
1010 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
1011 column_count, value_count);
1012 return NULL;
1013 }
1014
1015 res = open_database(config);
1016 if (res != 0) {
1017 return NULL;
1018 }
1019
1020 ast_log(LOG_NOTICE, "%s->%s: Logging %s records to table '%s'\n",
1021 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
1022 RECORD_TYPE_STR(config->record_type),
1023 config->db_table);
1024
1025 rtn_config = config;
1026 config = NULL;
1027 return rtn_config;
1028}
1029
1030/*!
1031 * \internal
1032 * \brief Load all the categories in a database config file.
1033 *
1034 * \param record_type
1035 * \param configs
1036 * \param config_filename
1037 * \param reload
1038 * \retval 0 on success or reload not needed.
1039 * \retval -1 on failure.
1040 */
1041static int load_database_config_file(enum cdrel_record_type record_type, struct cdrel_configs *configs,
1042 const char *config_filename, int reload)
1043{
1044 struct ast_config *cfg;
1045 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1046 struct ast_category *category = NULL;
1047
1048 ast_debug(1, "%s: %soading\n", config_filename, reload ? "Rel" : "L");
1049 cfg = ast_config_load(config_filename, config_flags);
1050 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
1051 ast_log(LOG_ERROR, "Unable to load %s. Not logging %ss to custom database\n",
1052 config_filename, RECORD_TYPE_STR(record_type));
1053 return -1;
1054 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
1055 ast_debug(1, "%s: Config file unchanged, not reloading\n", config_filename);
1056 return 0;
1057 }
1058
1059 while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
1060 struct cdrel_config *config = NULL;
1061
1062 config = load_database_config(record_type, category, config_filename);
1063 if (!config) {
1064 continue;
1065 }
1066
1067 if (AST_VECTOR_APPEND(configs, config) != 0) {
1069 break;
1070 }
1071 }
1072
1073 ast_config_destroy(cfg);
1074
1075 ast_log(LOG_NOTICE, "%s: Loaded %d configs\n", config_filename, (int)AST_VECTOR_SIZE(configs));
1076
1077 /* Only fail if no configs were valid. */
1078 return AST_VECTOR_SIZE(configs) > 0 ? 0 : -1;
1079}
1080
1081/*!
1082 * \internal
1083 * \brief Load a legacy config from a single entry in the "mappings" castegory.
1084 *
1085 * \param record_type
1086 * \param config_filename
1087 * \param output_filename
1088 * \param template
1089 * \return config or NULL.
1090 */
1092 const char *config_filename, const char *output_filename, const char *template)
1093{
1094 struct cdrel_config *config = NULL;
1095 int field_check_passed = 0;
1096 int res = 0;
1098
1099 res = AST_VECTOR_INIT(fields, 25);
1100 if (res != 0) {
1101 return NULL;
1102 }
1103
1104 /*
1105 * Let's try and and parse a legacy config to see if we can turn
1106 * it into an advanced condig.
1107 */
1109 output_filename, template, fields);
1110
1111 /*
1112 * If we couldn't, treat as legacy.
1113 */
1114 if (!field_check_passed) {
1116 config_filename, output_filename, template);
1117 ast_log(LOG_NOTICE, "%s->%s: Logging legacy %s records\n",
1118 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
1119 RECORD_TYPE_STR(config->record_type));
1120 return config;
1121 }
1122
1124 config_filename, output_filename, template);
1125 if (!config) {
1126 return NULL;
1127 }
1128 config->format_type = cdrel_format_dsv;
1129 config->quote[0] = '"';
1130 config->quote_escape[0] = '"';
1131 config->separator[0] = ',';
1132 config->quoting_method = cdrel_quoting_method_all;
1133
1134 if (AST_VECTOR_INIT(&config->fields, AST_VECTOR_SIZE(fields)) != 0) {
1135 return NULL;
1136 }
1137
1138 if (load_fields(config, fields) != 0) {
1139 return NULL;
1140 }
1141
1142 ast_log(LOG_NOTICE, "%s->%s: Logging legacy %s records as advanced\n",
1143 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
1144 RECORD_TYPE_STR(config->record_type));
1145
1146 return config;
1147}
1148
1149/*!
1150 * \internal
1151 * \brief Load an advanced config from a config file category.
1152 *
1153 * \param record_type
1154 * \param category
1155 * \param config_filename
1156 * \return config or NULL.
1157 */
1159 struct ast_category *category, const char *config_filename)
1160{
1161 const char *category_name = ast_category_get_name(category);
1163 struct cdrel_config *rtn_config = NULL;
1164 const char *value;
1165 int res = 0;
1166 const char *fields_value = ast_variable_find(category, "fields");
1167 char *tmp_fields = NULL;
1169
1170 if (ast_strlen_zero(fields_value)) {
1171 ast_log(LOG_WARNING, "%s->%s: Missing 'fields' parameter\n",
1172 cdrel_basename(config_filename), category_name);
1173 return NULL;
1174 }
1175
1177 config_filename, category_name, fields_value);
1178
1179 value = ast_variable_find(category, "format");
1180 if (!ast_strlen_zero(value)) {
1181 if (ast_strings_equal(value, "json")) {
1182 config->format_type = cdrel_format_json;
1183 config->separator[0] = ',';
1184 config->quote[0] = '"';
1185 config->quote_escape[0] = '\\';
1186 config->quoting_method = cdrel_quoting_method_non_numeric;
1187 } else if (ast_strings_equal(value, "dsv")) {
1188 config->format_type = cdrel_format_dsv;
1189 } else {
1190 ast_log(LOG_WARNING, "%s->%s: Invalid format '%s'\n",
1191 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename), value);
1192 return NULL;
1193 }
1194 }
1195
1196 if (config->format_type != cdrel_format_json) {
1197 value = ast_variable_find(category, "separator_character");
1198 if (!ast_strlen_zero(value)) {
1200 }
1201
1202 value = ast_variable_find(category, "quote_character");
1203 if (!ast_strlen_zero(value)) {
1204 ast_copy_string(config->quote, value, 2);
1205 }
1206
1207 value = ast_variable_find(category, "quote_escape_character");
1208 if (!ast_strlen_zero(value)) {
1209 ast_copy_string(config->quote_escape, value, 2);
1210 }
1211
1212 value = ast_variable_find(category, "quoting_method");
1213 if (!ast_strlen_zero(value)) {
1214 if (ast_strings_equal(value, "all")) {
1215 config->quoting_method = cdrel_quoting_method_all;
1216 } else if (ast_strings_equal(value, "minimal")) {
1217 config->quoting_method = cdrel_quoting_method_minimal;
1218 } else if (ast_strings_equal(value, "non_numeric")) {
1219 config->quoting_method = cdrel_quoting_method_non_numeric;
1220 } else if (ast_strings_equal(value, "none")) {
1221 config->quoting_method = cdrel_quoting_method_none;
1222 } else {
1223 ast_log(LOG_WARNING, "%s->%s: Invalid quoting method '%s'\n",
1224 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename), value);
1225 return NULL;
1226 }
1227 }
1228 }
1229
1230 res = AST_VECTOR_INIT(fields, 20);
1231 if (res != 0) {
1232 return NULL;
1233 }
1234 tmp_fields = ast_strdupa(fields_value);
1235 while((value = ast_strsep(&tmp_fields, ',', AST_STRSEP_TRIM))) {
1237 if (res != 0) {
1238 return NULL;
1239 }
1240 }
1241
1242 if (AST_VECTOR_INIT(&config->fields, AST_VECTOR_SIZE(fields)) != 0) {
1243 return NULL;
1244 }
1245
1246 if (load_fields(config, fields) != 0) {
1247 return NULL;
1248 }
1249
1250 ast_log(LOG_NOTICE, "%s->%s: Logging %s records\n",
1251 cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
1252 RECORD_TYPE_STR(config->record_type));
1253
1254 rtn_config = config;
1255 config = NULL;
1256
1257 return rtn_config;
1258}
1259
1260/*!
1261 * \internal
1262 * \brief Load a legacy configs from the "mappings" category.
1263 *
1264 * \param record_type
1265 * \param configs
1266 * \param category
1267 * \param config_filename
1268 * \retval 0 on success.
1269 * \retval -1 on failure.
1270 */
1272 struct cdrel_configs *configs, struct ast_category *category,
1273 const char *config_filename)
1274{
1275 struct ast_variable *var = NULL;
1276
1277 for (var = ast_category_first(category); var; var = var->next) {
1278 struct cdrel_config *config = NULL;
1279
1280 if (ast_strlen_zero(var->name) || ast_strlen_zero(var->value)) {
1281 ast_log(LOG_WARNING, "%s: %s mapping must have both a filename and a template at line %d\n",
1282 cdrel_basename(config_filename), RECORD_TYPE_STR(config->record_type), var->lineno);
1283 continue;
1284 }
1285
1287 if (!config) {
1288 continue;
1289 }
1290
1291 if (AST_VECTOR_APPEND(configs, config) != 0) {
1293 return -1;
1294 }
1295 }
1296
1297 return 0;
1298}
1299
1300/*!
1301 * \internal
1302 * \brief Load all text file backend configs from a config file.
1303 *
1304 * \param record_type
1305 * \param configs
1306 * \param config_filename
1307 * \param reload
1308 * \return
1309 */
1311 struct cdrel_configs *configs, const char *config_filename, int reload)
1312{
1313 struct ast_config *cfg;
1314 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1315 struct ast_category *category = NULL;
1316
1317 ast_debug(1, "%s: %soading\n", config_filename, reload ? "Rel" : "L");
1318 cfg = ast_config_load(config_filename, config_flags);
1319 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
1320 ast_log(LOG_ERROR, "Unable to load %s. Not logging %ss to custom files\n",
1321 config_filename, RECORD_TYPE_STR(record_type));
1322 return -1;
1323 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
1324 ast_debug(1, "%s: Config file unchanged, not reloading\n", config_filename);
1325 return 0;
1326 }
1327
1328 while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
1329 const char *category_name = ast_category_get_name(category);
1330
1331 if (ast_strings_equal(category_name, "mappings")) {
1333 } else {
1336 if (!config) {
1337 continue;
1338 }
1339 if (AST_VECTOR_APPEND(configs, config) != 0) {
1341 return -1;
1342 }
1343 }
1344 }
1345
1346 ast_config_destroy(cfg);
1347
1348 ast_log(LOG_NOTICE, "%s: Loaded %d configs\n", config_filename, (int)AST_VECTOR_SIZE(configs));
1349
1350 /* Only fail if no configs were valid. */
1351 return AST_VECTOR_SIZE(configs) > 0 ? 0 : -1;
1352}
1353
1354static int register_backend(enum cdrel_record_type record_type, const char *backend_name, void *log_cb)
1355{
1356 switch(record_type) {
1357 case cdrel_record_cdr:
1358 return ast_cdr_register(backend_name, "", log_cb);
1359 case cdrel_backend_db:
1360 return ast_cel_backend_register(backend_name, log_cb);
1361 default:
1362 return -1;
1363 }
1364}
1365
1366static int unregister_backend(enum cdrel_record_type record_type, const char *backend_name)
1367{
1368 switch(record_type) {
1369 case cdrel_record_cdr:
1370 return ast_cdr_unregister(backend_name);
1371 case cdrel_record_cel:
1372 return ast_cel_backend_unregister(backend_name);
1373 default:
1374 return -1;
1375 }
1376}
1377
1379 struct cdrel_configs *configs, const char *filename, int reload)
1380{
1381 switch(output_type) {
1382 case cdrel_backend_text:
1384 case cdrel_backend_db:
1386 default:
1387 return -1;
1388 }
1389}
1390
1392 struct cdrel_configs **configs, const char *filename)
1393{
1394 int res = 0;
1395 struct cdrel_configs *old_configs = *configs;
1396 struct cdrel_configs *new_configs = NULL;
1397
1398 /*
1399 * Save new config to a temporary vector to make sure the
1400 * configs are valid before swapping them in.
1401 */
1402 new_configs = ast_malloc(sizeof(*new_configs));
1403 if (!new_configs) {
1405 }
1406
1407 if (AST_VECTOR_INIT(new_configs, AST_VECTOR_SIZE(old_configs)) != 0) {
1409 }
1410
1411 res = load_config_file(output_type, record_type, new_configs, filename, 1);
1412 if (res != 0) {
1413 AST_VECTOR_RESET(new_configs, config_free);
1414 AST_VECTOR_PTR_FREE(new_configs);
1416 }
1417
1418 /* Now swap the new ones in. */
1419 *configs = new_configs;
1420
1421 /* Free the old ones. */
1422 AST_VECTOR_RESET(old_configs, config_free);
1423 AST_VECTOR_PTR_FREE(old_configs);
1424
1426
1427
1428 return -1;
1429}
1430
1431struct cdrel_configs *cdrel_load_module(enum cdrel_backend_type backend_type,
1432 enum cdrel_record_type record_type, const char *filename, const char *backend_name,
1433 void *log_cb)
1434{
1435 struct cdrel_configs *configs = ast_calloc(1, sizeof(*configs));
1436 if (!configs) {
1437 return NULL;
1438 }
1439 ast_debug(1, "Loading %s %s\n", RECORD_TYPE_STR(record_type), MODULE_TYPE_STR(backend_type));
1440
1441 if (AST_VECTOR_INIT(configs, 5) != 0) {
1442 cdrel_unload_module(backend_type, record_type, configs, backend_name);
1443 return NULL;
1444 }
1445
1446 if (load_config_file(backend_type, record_type, configs, filename, 0) != 0) {
1447 cdrel_unload_module(backend_type, record_type, configs, backend_name);
1448 return NULL;
1449 }
1450
1451 if (register_backend(record_type, backend_name, log_cb)) {
1452 cdrel_unload_module(backend_type, record_type, configs, backend_name);
1453 return NULL;
1454 }
1455
1456 return configs;
1457}
1458
1459int cdrel_unload_module(enum cdrel_backend_type backend_type, enum cdrel_record_type record_type,
1460 struct cdrel_configs *configs, const char *backend_name)
1461{
1462 if (unregister_backend(record_type, backend_name) != 0) {
1463 return -1;
1464 }
1465
1468
1469 return 0;
1470}
#define var
Definition ast_expr2f.c:605
#define ast_free(a)
Definition astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition astmm.h:191
#define ast_log
Definition astobj2.c:42
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition cdr.c:3121
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition cdr.c:3076
Private header for res_cdrel_custom.
const char * cdrel_basename(const char *path)
const struct cdrel_field * get_registered_field_by_name(enum cdrel_record_type record_type, const char *name)
Definition registry.c:113
enum cdrel_data_type cdrel_data_type_from_str(const char *str)
#define DATA_TYPE_STR(_dt)
Definition cdrel.h:92
#define MODULE_TYPE_STR(_mt)
Definition cdrel.h:45
cdrel_data_type
Definition cdrel.h:72
@ cdrel_data_type_strings_end
Definition cdrel.h:82
@ cdrel_type_uservar
Definition cdrel.h:78
@ cdrel_type_literal
Definition cdrel.h:75
@ cdrel_type_string
Definition cdrel.h:73
@ cdrel_data_type_end
Definition cdrel.h:88
@ cdrel_type_timeval
Definition cdrel.h:74
@ cdrel_quoting_method_all
Definition cdrel.h:62
@ cdrel_quoting_method_none
Definition cdrel.h:61
@ cdrel_quoting_method_non_numeric
Definition cdrel.h:64
@ cdrel_quoting_method_minimal
Definition cdrel.h:63
#define RECORD_TYPE_STR(_rt)
Definition cdrel.h:42
@ cdrel_format_json
Definition cdrel.h:49
@ cdrel_format_dsv
Definition cdrel.h:48
@ cdrel_format_sql
Definition cdrel.h:50
@ cdrel_flag_literal
Definition cdrel.h:108
@ cdrel_flag_quote
Definition cdrel.h:104
@ cdrel_flag_format_spec
Definition cdrel.h:109
@ cdrel_flag_noquote
Definition cdrel.h:105
@ cdrel_flag_uservar
Definition cdrel.h:107
@ cdrel_flag_type_forced
Definition cdrel.h:106
cdrel_config_type
Definition cdrel.h:54
@ cdrel_config_legacy
Definition cdrel.h:55
@ cdrel_config_advanced
Definition cdrel.h:56
cdrel_dummy_channel_alloc cdrel_dummy_channel_allocators[cdrel_format_type_end]
const char * cdrel_get_field_flags(struct ast_flags *flags, struct ast_str **str)
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition cel.c:1824
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition cel.c:1836
static PGresult * result
Definition cel_pgsql.c:84
static const char config[]
static char * config_filename
Definition extconf.c:2118
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
const char * ast_category_get_name(const struct ast_category *category)
Return the name of the category.
#define CONFIG_STATUS_FILEUNCHANGED
@ CONFIG_FLAG_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
int ast_parse_arg(const char *arg, enum ast_parse_flags flags, void *p_result,...)
The argument parsing routine.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition extconf.c:1287
struct ast_variable * ast_category_first(struct ast_category *cat)
given a pointer to a category, return the root variable.
const char * ast_variable_find(const struct ast_category *category, const char *variable)
Gets a variable value from a specific category structure by name.
struct ast_category * ast_category_browse_filtered(struct ast_config *config, const char *category_name, struct ast_category *prev, const char *filter)
Browse categories with filters.
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
#define ast_mutex_init(pmutex)
Definition lock.h:193
#define ast_mutex_destroy(a)
Definition lock.h:195
Asterisk module definitions.
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition module.h:78
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_LOG_DIR
Definition options.c:160
static const char * special_vars[]
static void config_free(struct cdrel_config *config)
static struct cdrel_config * load_database_config(enum cdrel_record_type record_type, struct ast_category *category, const char *config_filename)
static int unregister_backend(enum cdrel_record_type record_type, const char *backend_name)
static struct cdrel_config * load_text_file_legacy_config(enum cdrel_record_type record_type, const char *config_filename, const char *output_filename, const char *template)
static struct field_parse_result parse_field(enum cdrel_record_type record_type, char *input_field_template)
static void field_template_vector_free(struct ast_vector_string *fields)
static int load_fields(struct cdrel_config *config, struct ast_vector_string *fields)
static struct cdrel_field * field_alloc(struct cdrel_config *config, const char *input_field_template)
static int register_backend(enum cdrel_record_type record_type, const char *backend_name, void *log_cb)
static int load_text_file_legacy_mappings(enum cdrel_record_type record_type, struct cdrel_configs *configs, struct ast_category *category, const char *config_filename)
static int open_database(struct cdrel_config *config)
int cdrel_reload_module(enum cdrel_backend_type output_type, enum cdrel_record_type record_type, struct cdrel_configs **configs, const char *filename)
Perform module reload.
static int load_config_file(enum cdrel_backend_type output_type, enum cdrel_record_type record_type, struct cdrel_configs *configs, const char *filename, int reload)
static int load_database_config_file(enum cdrel_record_type record_type, struct cdrel_configs *configs, const char *config_filename, int reload)
int cdrel_unload_module(enum cdrel_backend_type backend_type, enum cdrel_record_type record_type, struct cdrel_configs *configs, const char *backend_name)
Perform module unload.
struct cdrel_configs * cdrel_load_module(enum cdrel_backend_type backend_type, enum cdrel_record_type record_type, const char *filename, const char *backend_name, void *log_cb)
Perform initial module load.
static const char * dirname_map[cdrel_backend_type_end][cdrel_record_type_end]
static char * make_stmt_placeholders(int columns)
static struct cdrel_config * load_text_file_advanced_config(enum cdrel_record_type record_type, struct ast_category *category, const char *config_filename)
static int parse_legacy_template(enum cdrel_record_type record_type, const char *config_filename, const char *output_filename, const char *input_template, struct ast_vector_string *fields)
static int load_text_file_config_file(enum cdrel_record_type record_type, struct cdrel_configs *configs, const char *config_filename, int reload)
static int load_database_columns(struct cdrel_config *config, const char *columns, int *column_count)
static const char * allowed_functions[]
cdrel_backend_type
@ cdrel_backend_db
@ cdrel_backend_text
@ cdrel_backend_type_end
cdrel_record_type
@ cdrel_record_cel
@ cdrel_record_cdr
@ cdrel_record_type_end
static int reload(void)
#define NULL
Definition resample.c:96
#define ast_calloc_with_stringfields(n, type, size)
Allocate a structure with embedded stringfields in a single allocation.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
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
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition strings.c:238
#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
char * ast_unescape_c(char *s)
Convert some C escape sequences.
Definition utils.c:2017
static int force_inline attribute_pure ast_ends_with(const char *str, const char *suffix)
Checks whether a string ends with another.
Definition strings.h:116
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition strings.h:65
#define ast_str_tmp(init_len, __expr)
Provides a temporary ast_str and returns a copy of its buffer.
Definition strings.h:1189
@ AST_STRSEP_TRIM
Definition strings.h:256
@ AST_STRSEP_STRIP
Definition strings.h:255
char * ast_strip_quoted(char *s, const char *beg_quotes, const char *end_quotes)
Strip leading/trailing whitespace and quotes from a string.
Definition utils.c:1852
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition strings.h:659
char *attribute_pure ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition strings.h:761
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition strings.h:425
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition strings.h:223
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition utils.c:1869
Structure used to handle boolean flags.
Definition utils.h:220
Support for dynamic strings.
Definition strings.h:623
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
String vector definitions.
Definition vector.h:55
struct cdrel_config::@455 fields
const ast_string_field output_filename
Definition cdrel.h:289
enum cdrel_record_type record_type
Definition cdrel.h:282
char data[1]
Definition cdrel.h:277
enum cdrel_data_type input_data_type
Definition cdrel.h:274
int value
Definition syslog.c:37
static struct test_val c
static struct prometheus_general_config * config_alloc(void)
#define ast_test_flag(p, flag)
Definition utils.h:64
#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:981
#define ast_assert(a)
Definition utils.h:779
#define MIN(a, b)
Definition utils.h:252
#define ast_clear_flag(p, flag)
Definition utils.h:78
#define ast_set_flag(p, flag)
Definition utils.h:71
#define AST_VECTOR_RESET(vec, cleanup)
Reset vector.
Definition vector.h:636
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:620
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition vector.h:185
#define AST_VECTOR_PTR_FREE(vec)
Deallocates this vector pointer.
Definition vector.h:200
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition vector.h:124
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition vector.h:267
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition vector.h:691