diff --git a/build/modules.conf.in b/build/modules.conf.in index ec18c414f9..1af56eef31 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -105,6 +105,7 @@ event_handlers/mod_event_socket #event_handlers/mod_format_cdr #event_handlers/mod_json_cdr #event_handlers/mod_radius_cdr +#event_handlers/mod_odbc_cdr #event_handlers/mod_rayo #event_handlers/mod_snmp formats/mod_local_stream diff --git a/configure.ac b/configure.ac index ed3803823b..a11de35c84 100644 --- a/configure.ac +++ b/configure.ac @@ -1558,6 +1558,7 @@ AC_CONFIG_FILES([Makefile src/mod/event_handlers/mod_format_cdr/Makefile src/mod/event_handlers/mod_json_cdr/Makefile src/mod/event_handlers/mod_radius_cdr/Makefile + src/mod/event_handlers/mod_odbc_cdr/Makefile src/mod/event_handlers/mod_rayo/Makefile src/mod/event_handlers/mod_snmp/Makefile src/mod/event_handlers/mod_event_zmq/Makefile @@ -1586,7 +1587,7 @@ AC_CONFIG_FILES([Makefile src/mod/say/mod_say_de/Makefile src/mod/say/mod_say_en/Makefile src/mod/say/mod_say_es/Makefile - src/mod/say/mod_say_es_ar/Makefile + src/mod/say/mod_say_es_ar/Makefile src/mod/say/mod_say_fa/Makefile src/mod/say/mod_say_fr/Makefile src/mod/say/mod_say_he/Makefile diff --git a/debian/control-modules b/debian/control-modules index a494304f6e..84b7d8d140 100644 --- a/debian/control-modules +++ b/debian/control-modules @@ -333,9 +333,9 @@ Description: mod_opus Adds mod_opus. Module: codecs/mod_sangoma_codec -Build-Depends: libsngtc-dev Description: mod_sangoma_codec Adds mod_sangoma_codec. +Build-Depends: libsngtc-dev Module: codecs/mod_silk Description: mod_silk @@ -500,6 +500,10 @@ Module: event_handlers/mod_json_cdr Description: mod_json_cdr Adds mod_json_cdr. +Module: event_handlers/mod_odbc_cdr +Description: mod_odbc_cdr + Adds mod_odbc_cdr. + Module: event_handlers/mod_radius_cdr Description: mod_radius_cdr Adds mod_radius_cdr. @@ -630,6 +634,10 @@ Module: say/mod_say_es Description: mod_say_es Adds mod_say_es. +Module: say/mod_say_es_ar +Description: mod_say_es_ar + Adds mod_say_es_ar. + Module: say/mod_say_fa Description: mod_say_fa Adds mod_say_fa. diff --git a/debian/freeswitch-sysvinit.freeswitch.init b/debian/freeswitch-sysvinit.freeswitch.init index f393ff48a6..98be1af324 100644 --- a/debian/freeswitch-sysvinit.freeswitch.init +++ b/debian/freeswitch-sysvinit.freeswitch.init @@ -19,8 +19,7 @@ DESC=freeswitch NAME=freeswitch DAEMON=/usr/bin/freeswitch USER=freeswitch -GROUP=freeswitch -DAEMON_ARGS="-u $USER -g $GROUP -ncwait" +DAEMON_ARGS="-u $USER -ncwait" CONFDIR=/etc/$NAME RUNDIR=/var/run/$NAME PIDFILE=$RUNDIR/$NAME.pid @@ -43,7 +42,7 @@ do_start() { # Directory in /var/run may disappear on reboot (e.g. when tmpfs used for /var/run). mkdir -p $RUNDIR - chown -R $USER:$GROUP $RUNDIR + chown -R $USER: $RUNDIR chmod -R ug=rwX,o= $RUNDIR start-stop-daemon --start --quiet \ diff --git a/debian/rules b/debian/rules index eefc3bc277..cf6a7ed1ea 100755 --- a/debian/rules +++ b/debian/rules @@ -55,7 +55,6 @@ clean: dh $@ override_dh_auto_clean: - dh_clean .stamp-bootstrap: @$(call show_vars) diff --git a/freeswitch.spec b/freeswitch.spec index 08596a8f4f..ca95e20657 100644 --- a/freeswitch.spec +++ b/freeswitch.spec @@ -129,6 +129,7 @@ Source10: http://files.freeswitch.org/downloads/libs/libmemcached-0.32.tar.gz Source11: http://files.freeswitch.org/downloads/libs/json-c-0.9.tar.gz Source12: http://files.freeswitch.org/downloads/libs/opus-1.1-p2.tar.gz Source13: http://files.freeswitch.org/downloads/libs/v8-3.24.14.tar.bz2 +Source14: http://files.freeswitch.org/downloads/libs/mongo-c-driver-0.92.2.tar.gz Prefix: %{prefix} diff --git a/scripts/perl/mkgws.pl b/scripts/perl/mkgws.pl new file mode 100644 index 0000000000..6743c969d0 --- /dev/null +++ b/scripts/perl/mkgws.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl +# +# Make bulk gateway xml from csv file. +# + +open(CSV, "gateways.csv"); +my @data = ; +close(CSV); + +foreach my $line (@data) { + chomp($line); + my ($gwname, $username, $password) = split(/,/, $line); + print < + + + + + + + + + + + + + + + + + + + + +XML + +} diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h index e8b6843d14..523e3a4768 100644 --- a/src/include/switch_utils.h +++ b/src/include/switch_utils.h @@ -541,6 +541,28 @@ SWITCH_DECLARE(int) switch_build_uri(char *uri, switch_size_t size, const char * #define SWITCH_STATUS_IS_BREAK(x) (x == SWITCH_STATUS_BREAK || x == 730035 || x == 35 || x == SWITCH_STATUS_INTR) + +#ifdef _MSC_VER + +#define switch_errno() WSAGetLastError() + +static inline int switch_errno_is_break(int errcode) +{ + return errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEINTR; +} + +#else + +#define switch_errno() errno + +static inline int switch_errno_is_break(int errcode) +{ + return errcode == EAGAIN || errcode == EWOULDBLOCK || errcode == EINPROGRESS || errcode == EINTR || errcode == ETIMEDOUT; +} + +#endif + + /*! \brief Return a printable name of a switch_priority_t \param priority the priority to get the name of diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c index 1ea3045de6..38b2f893d4 100644 --- a/src/mod/applications/mod_conference/mod_conference.c +++ b/src/mod/applications/mod_conference/mod_conference.c @@ -2277,7 +2277,8 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe } if (conference->count > 1) { - if (conference->moh_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD)) { + if ((conference->moh_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD)) || + (switch_test_flag(conference, CFLAG_WAIT_MOD) && !switch_true(switch_channel_get_variable(channel, "conference_permanent_wait_mod_moh")))) { /* stop MoH if any */ conference_stop_file(conference, FILE_STOP_ASYNC); } @@ -2287,10 +2288,9 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe if (switch_test_flag(conference, CFLAG_ENTER_SOUND)) { if (!zstr(enter_sound)) { conference_play_file(conference, (char *)enter_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + switch_core_session_get_channel(member->session), 0); } else { - conference_play_file(conference, conference->enter_sound, CONF_DEFAULT_LEADIN, switch_core_session_get_channel(member->session), - !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + conference_play_file(conference, conference->enter_sound, CONF_DEFAULT_LEADIN, switch_core_session_get_channel(member->session), 0); } } } @@ -2316,7 +2316,7 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe if (conference->alone_sound && !switch_test_flag(member, MFLAG_GHOST)) { conference_stop_file(conference, FILE_STOP_ASYNC); conference_play_file(conference, conference->alone_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), 1); + switch_core_session_get_channel(member->session), 0); } else { switch_snprintf(msg, sizeof(msg), "You are currently the only person in this conference."); conference_member_say(member, msg, CONF_DEFAULT_LEADIN); @@ -2683,7 +2683,7 @@ static switch_status_t conference_del_member(conference_obj_t *conference, confe if (member->session && (exit_sound = switch_channel_get_variable(switch_core_session_get_channel(member->session), "conference_exit_sound"))) { conference_play_file(conference, (char *)exit_sound, CONF_DEFAULT_LEADIN, - switch_core_session_get_channel(member->session), !switch_test_flag(conference, CFLAG_WAIT_MOD) ? 0 : 1); + switch_core_session_get_channel(member->session), 0); } @@ -2786,12 +2786,16 @@ static switch_status_t conference_del_member(conference_obj_t *conference, confe || (switch_test_flag(conference, CFLAG_DYNAMIC) && (conference->count + conference->count_ghosts == 0))) { switch_set_flag(conference, CFLAG_DESTRUCT); } else { + if (!switch_true(switch_channel_get_variable(channel, "conference_permanent_wait_mod_moh")) && switch_test_flag(conference, CFLAG_WAIT_MOD)) { + /* Stop MOH if any */ + conference_stop_file(conference, FILE_STOP_ASYNC); + } if (!exit_sound && conference->exit_sound && switch_test_flag(conference, CFLAG_EXIT_SOUND)) { conference_play_file(conference, conference->exit_sound, 0, channel, 0); } if (conference->count == 1 && conference->alone_sound && !switch_test_flag(conference, CFLAG_WAIT_MOD) && !switch_test_flag(member, MFLAG_GHOST)) { conference_stop_file(conference, FILE_STOP_ASYNC); - conference_play_file(conference, conference->alone_sound, 0, channel, 1); + conference_play_file(conference, conference->alone_sound, 0, channel, 0); } } @@ -3146,7 +3150,7 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v if (conference->perpetual_sound && !conference->async_fnode) { conference_play_file(conference, conference->perpetual_sound, CONF_DEFAULT_LEADIN, NULL, 1); } else if (conference->moh_sound && ((nomoh == 0 && conference->count == 1) - || switch_test_flag(conference, CFLAG_WAIT_MOD)) && !conference->async_fnode) { + || switch_test_flag(conference, CFLAG_WAIT_MOD)) && !conference->async_fnode && !conference->fnode) { conference_play_file(conference, conference->moh_sound, CONF_DEFAULT_LEADIN, NULL, 1); } diff --git a/src/mod/applications/mod_fifo/mod_fifo.c b/src/mod/applications/mod_fifo/mod_fifo.c index ac0d1167b7..d6877ef8dc 100644 --- a/src/mod/applications/mod_fifo/mod_fifo.c +++ b/src/mod/applications/mod_fifo/mod_fifo.c @@ -79,6 +79,15 @@ SWITCH_MODULE_DEFINITION(mod_fifo, mod_fifo_load, mod_fifo_shutdown, NULL); * The /enterprise/ outbound strategy does not preserve the caller ID * of the caller thereby allowing deliver of callers to agents at the * fastest possible rate. + * + * outbound_per_cycle is used to define the maximum number of agents + * who will be available to answer a single caller. In ringall this + * maximum is the number who will be called, in enterprise the need defines + * how many agents will be called. outbound_per_cycle_min will define + * the minimum agents who will be called to answer a caller regardless of + * need, giving the enterprise strategy the ability to ring through more + * than one agent for one caller. + * * ## Manual calls * @@ -391,6 +400,7 @@ struct fifo_node { long busy; int is_static; int outbound_per_cycle; + int outbound_per_cycle_min; char *outbound_name; outbound_strategy_t outbound_strategy; int ring_timeout; @@ -1985,6 +1995,21 @@ static int place_call_enterprise_callback(void *pArg, int argc, char **argv, cha * the results. The enterprise strategy handler can simply take each * member one at a time, so the `place_call_enterprise_callback` takes * care of invoking the handler. + * + * Within the ringall call strategy outbound_per_cycle is used to define + * how many agents exactly are assigned to the caller. With ringall if + * multiple callers are calling in and one is answered, because the call + * is assigned to all agents the call to the agents that is not answered + * will be lose raced and the other agents will drop the call before the + * next one will begin to ring. When oubound_per_cycle is used in the + * enterprise strategy it acts as a maximum value for how many agents + * are rung at once on any call, the caller is not assigned to any agent + * until the call is answered. Enterprise only rings the number of phones + * that are needed, so outbound_per_cycle as a max does not give you the + * effect of ringall. outbound_per_cycle_min defines how many agents minimum + * will be rung by an incoming caller through fifo, which can give a ringall + * effect. outbound_per_cycle and outbound_per_cycle_min both default to 1. + * */ static void find_consumers(fifo_node_t *node) { @@ -2005,6 +2030,8 @@ static void find_consumers(fifo_node_t *node) if (node->outbound_per_cycle && node->outbound_per_cycle < need) { need = node->outbound_per_cycle; + } else if (node->outbound_per_cycle_min && node->outbound_per_cycle_min > need) { + need = node->outbound_per_cycle_min; } fifo_execute_sql_callback(globals.sql_mutex, sql, place_call_enterprise_callback, &need); @@ -4045,6 +4072,9 @@ static void list_node(fifo_node_t *node, switch_xml_t x_report, int *off, int ve switch_snprintf(tmp, sizeof(buffer), "%u", node->outbound_per_cycle); switch_xml_set_attr_d(x_fifo, "outbound_per_cycle", tmp); + switch_snprintf(tmp, sizeof(buffer), "%u", node->outbound_per_cycle_min); + switch_xml_set_attr_d(x_fifo, "outbound_per_cycle_min", tmp); + switch_snprintf(tmp, sizeof(buffer), "%u", node->ring_timeout); switch_xml_set_attr_d(x_fifo, "ring_timeout", tmp); @@ -4088,6 +4118,7 @@ void node_dump(switch_stream_handle_t *stream) stream->write_function(stream, "node: %s\n" " outbound_name: %s\n" " outbound_per_cycle: %d" + " outbound_per_cycle_min: %d" " outbound_priority: %d" " outbound_strategy: %s\n" " has_outbound: %d\n" @@ -4096,7 +4127,7 @@ void node_dump(switch_stream_handle_t *stream) " ready: %d\n" " waiting: %d\n" , - node->name, node->outbound_name, node->outbound_per_cycle, + node->name, node->outbound_name, node->outbound_per_cycle, node->outbound_per_cycle_min, node->outbound_priority, print_strategy(node->outbound_strategy), node->has_outbound, node->outbound_priority, @@ -4508,6 +4539,13 @@ static switch_status_t load_config(int reload, int del_all) node->has_outbound = 1; } + node->outbound_per_cycle_min = 1; + if ((val = switch_xml_attr(fifo, "outbound_per_cycle_min"))) { + if (!((i = atoi(val)) < 0)) { + node->outbound_per_cycle_min = i; + } + } + if ((val = switch_xml_attr(fifo, "retry_delay"))) { if ((i = atoi(val)) < 0) i = 0; node->retry_delay = i; diff --git a/src/mod/event_handlers/mod_odbc_cdr/Makefile.am b/src/mod/event_handlers/mod_odbc_cdr/Makefile.am new file mode 100644 index 0000000000..ce799a3e1e --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_odbc_cdr + +mod_LTLIBRARIES = mod_odbc_cdr.la +mod_odbc_cdr_la_SOURCES = mod_odbc_cdr.c +mod_odbc_cdr_la_CFLAGS = $(AM_CFLAGS) +mod_odbc_cdr_la_LIBADD = $(switch_builddir)/libfreeswitch.la +mod_odbc_cdr_la_LDFLAGS = -avoid-version -module -no-undefined -shared + diff --git a/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml b/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml new file mode 100644 index 0000000000..0e764d14ec --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/conf/autoload_configs/odbc_cdr.conf.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + +
+
+
diff --git a/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c b/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c new file mode 100644 index 0000000000..edfdbf08c4 --- /dev/null +++ b/src/mod/event_handlers/mod_odbc_cdr/mod_odbc_cdr.c @@ -0,0 +1,565 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2012, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Emmanuel Schmidbauer + * + * mod_odbc_cdr.c + * + */ + +#include "switch.h" + +#define ODBC_CDR_SQLITE_DB_NAME "odbc_cdr" + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_odbc_cdr_shutdown); +SWITCH_MODULE_LOAD_FUNCTION(mod_odbc_cdr_load); +SWITCH_MODULE_DEFINITION(mod_odbc_cdr, mod_odbc_cdr_load, mod_odbc_cdr_shutdown, NULL); + +static const char *global_cf = "odbc_cdr.conf"; + +typedef enum { + ODBC_CDR_LOG_A, + ODBC_CDR_LOG_B, + ODBC_CDR_LOG_BOTH +} odbc_cdr_log_leg_t; + +typedef enum { + ODBC_CDR_CSV_ALWAYS, + ODBC_CDR_CSV_NEVER, + ODBC_CDR_CSV_ON_FAIL +} odbc_cdr_write_csv_t; + +static struct { + char *odbc_dsn; + char *dbname; + char *csv_path; + char *csv_fail_path; + odbc_cdr_log_leg_t log_leg; + odbc_cdr_write_csv_t write_csv; + switch_bool_t debug_sql; + switch_hash_t *table_hash; + uint32_t running; + switch_mutex_t *mutex; + switch_memory_pool_t *pool; +} globals; + +struct table_profile { + char *name; + odbc_cdr_log_leg_t log_leg; + switch_hash_t *field_hash; + uint32_t flags; + switch_mutex_t *mutex; + switch_memory_pool_t *pool; +}; +typedef struct table_profile table_profile_t; + +static table_profile_t *load_table(const char *table_name) +{ + table_profile_t *table = NULL; + switch_xml_t x_tables, cfg, xml, x_table, x_field; + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + return table; + } + + if (!(x_tables = switch_xml_child(cfg, "tables"))) { + goto end; + } + + if ((x_table = switch_xml_find_child(x_tables, "table", "name", table_name))) { + switch_memory_pool_t *pool; + char *table_log_leg = (char *) switch_xml_attr_soft(x_table, "log-leg"); + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n"); + goto end; + } + + if (!(table = switch_core_alloc(pool, sizeof(table_profile_t)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n"); + switch_core_destroy_memory_pool(&pool); + goto end; + } + + table->pool = pool; + + switch_mutex_init(&table->mutex, SWITCH_MUTEX_NESTED, table->pool); + + table->name = switch_core_strdup(pool, table_name); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Found table [%s]\n", table->name); + + if (!strcasecmp(table_log_leg, "a-leg")) { + table->log_leg = ODBC_CDR_LOG_A; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log A-legs only\n", table->name); + } else if (!strcasecmp(table_log_leg, "b-leg")) { + table->log_leg = ODBC_CDR_LOG_B; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log B-legs only\n", table->name); + } else { + table->log_leg = ODBC_CDR_LOG_BOTH; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Set table [%s] to log both legs\n", table->name); + } + + switch_core_hash_init(&table->field_hash); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding fields to table [%s]\n", table->name); + + for (x_field = switch_xml_child(x_table, "field"); x_field; x_field = x_field->next) { + char *var = (char *) switch_xml_attr_soft(x_field, "name"); + char *val = (char *) switch_xml_attr_soft(x_field, "chan-var-name"); + char *value = NULL; + if (zstr(var) || zstr(val)) { + continue; // Ignore empty entries + } + value = switch_core_strdup(pool, val); + switch_core_hash_insert_locked(table->field_hash, var, value, table->mutex); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Field [%s] (%s) added to [%s]\n", var, val, table->name); + } + + switch_core_hash_insert(globals.table_hash, table->name, table); + } + +end: + + if (xml) { + switch_xml_free(xml); + } + + return table; +} + +switch_cache_db_handle_t *get_db_handle(void) +{ + switch_cache_db_handle_t *dbh = NULL; + char *dsn; + if (!zstr(globals.odbc_dsn)) { + dsn = globals.odbc_dsn; + } else { + dsn = globals.dbname; + } + if (switch_cache_db_get_db_handle_dsn(&dbh, dsn) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + dbh = NULL; + } + return dbh; +} + +static switch_status_t odbc_cdr_execute_sql_no_callback(char *sql) +{ + switch_cache_db_handle_t *dbh = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (!(dbh = get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + goto end; + } + + status = switch_cache_db_execute_sql(dbh, sql, NULL); + +end: + + switch_cache_db_release_db_handle(&dbh); + + return status; +} + +static void write_cdr(const char *path, const char *log_line) +{ + int fd = -1; +#ifdef _MSC_VER + if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) { +#else + if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) > -1) { +#endif + int wrote; + wrote = write(fd, log_line, (unsigned) strlen(log_line)); + wrote += write(fd, "\n", 1); + wrote++; + close(fd); + fd = -1; + } +} + +static switch_status_t odbc_cdr_reporting(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_memory_pool_t *pool = switch_core_session_get_pool(session); + switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(channel); + switch_hash_index_t *hi; + const void *var; + void *val; + switch_console_callback_match_t *matches = NULL; + switch_console_callback_match_node_t *m; + const char *uuid = NULL; + + if (globals.log_leg == ODBC_CDR_LOG_A && caller_profile->direction == SWITCH_CALL_DIRECTION_OUTBOUND) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Only logging A-Leg, ignoring B-leg\n"); + return SWITCH_STATUS_SUCCESS; + } else if (globals.log_leg == ODBC_CDR_LOG_B && caller_profile->direction == SWITCH_CALL_DIRECTION_INBOUND) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Only logging B-Leg, ignoring A-leg\n"); + return SWITCH_STATUS_SUCCESS; + } + + if (!(uuid = switch_channel_get_variable(channel, "uuid"))) { + uuid = switch_core_strdup(pool, caller_profile->uuid); + } + + // copy all table names from global hash + switch_mutex_lock(globals.mutex); + for (hi = switch_core_hash_first(globals.table_hash); hi; hi = switch_core_hash_next(&hi)) { + switch_core_hash_this(hi, &var, NULL, &val); + switch_console_push_match(&matches, (const char *) var); + } + switch_mutex_unlock(globals.mutex); + + if (matches) { + table_profile_t *table = NULL; + + // loop through table names + for (m = matches->head; m; m = m->next) { + char *table_name = m->val; + switch_bool_t started = SWITCH_FALSE; + switch_bool_t skip_leg = SWITCH_FALSE; + + switch_mutex_lock(globals.mutex); + table = switch_core_hash_find(globals.table_hash, table_name); + switch_mutex_unlock(globals.mutex); + + if (!table) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Table [%s] not found, ignoring leg\n", table_name); + skip_leg = SWITCH_TRUE; + } + + if (table->log_leg == ODBC_CDR_LOG_A && caller_profile->direction == SWITCH_CALL_DIRECTION_OUTBOUND) { + skip_leg = SWITCH_TRUE; + } + + if (table->log_leg == ODBC_CDR_LOG_B && caller_profile->direction == SWITCH_CALL_DIRECTION_INBOUND) { + skip_leg = SWITCH_TRUE; + } + + if (skip_leg == SWITCH_FALSE) { + switch_hash_index_t *i_hi = NULL; + const void *i_var; + void *i_val; + char *field_hash_key; + char *field_hash_val; + char *sql = NULL; + char *full_path = NULL; + switch_stream_handle_t stream_field = { 0 }; + switch_stream_handle_t stream_value = { 0 }; + switch_bool_t insert_fail = SWITCH_FALSE; + + SWITCH_STANDARD_STREAM(stream_field); + SWITCH_STANDARD_STREAM(stream_value); + + for (i_hi = switch_core_hash_first_iter( table->field_hash, i_hi); i_hi; i_hi = switch_core_hash_next(&i_hi)) { + const char *tmp; + switch_core_hash_this(i_hi, &i_var, NULL, &i_val); + field_hash_key = (char *) i_var; + field_hash_val = (char *) i_val; + + if ((tmp = switch_channel_get_variable(channel, field_hash_val))) { + if (started == SWITCH_FALSE) { + stream_field.write_function(&stream_field, "%s", field_hash_key); + stream_value.write_function(&stream_value, "'%s'", tmp); + } else { + stream_field.write_function(&stream_field, ", %s", field_hash_key); + stream_value.write_function(&stream_value, ", '%s'", tmp); + } + started = SWITCH_TRUE; + } + + } + switch_safe_free(i_hi); + + sql = switch_mprintf("INSERT INTO %s (%s) VALUES (%s)", table_name, stream_field.data, stream_value.data); + if (globals.debug_sql == SWITCH_TRUE) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "sql %s\n", sql); + } + if (odbc_cdr_execute_sql_no_callback(sql) == SWITCH_STATUS_FALSE) { + insert_fail = SWITCH_TRUE; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error executing query %s\n", sql); + } + + if (globals.write_csv == ODBC_CDR_CSV_ALWAYS) { + if (insert_fail == SWITCH_TRUE) { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_fail_path, SWITCH_PATH_SEPARATOR, uuid); + } else { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_path, SWITCH_PATH_SEPARATOR, uuid); + } + assert(full_path); + write_cdr(full_path, stream_value.data); + switch_safe_free(full_path); + } else if (globals.write_csv == ODBC_CDR_CSV_ON_FAIL && insert_fail == SWITCH_TRUE) { + full_path = switch_mprintf("%s%s%s.csv", globals.csv_fail_path, SWITCH_PATH_SEPARATOR, uuid); + assert(full_path); + write_cdr(full_path, stream_value.data); + switch_safe_free(full_path); + } + + switch_safe_free(sql); + + switch_safe_free(stream_field.data); + switch_safe_free(stream_value.data); + + } + + } + + switch_console_free_matches(&matches); + } + + switch_safe_free(hi); + + return SWITCH_STATUS_SUCCESS; +} + + +switch_state_handler_table_t odbc_cdr_state_handlers = { + /*.on_init */ NULL, + /*.on_routing */ NULL, + /*.on_execute */ NULL, + /*.on_hangup */ NULL, + /*.on_exchange_media */ NULL, + /*.on_soft_execute */ NULL, + /*.on_consume_media */ NULL, + /*.on_hibernate */ NULL, + /*.on_reset */ NULL, + /*.on_park */ NULL, + /*.on_reporting */ odbc_cdr_reporting, + /*.on_destroy */ NULL +}; + +static switch_status_t odbc_cdr_load_config(void) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_xml_t cfg, xml, settings, param, tables, table; + switch_cache_db_handle_t *dbh = NULL; + + switch_mutex_lock(globals.mutex); + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + status = SWITCH_STATUS_TERM; + goto end; + } + + globals.debug_sql = SWITCH_FALSE; + globals.log_leg = ODBC_CDR_LOG_BOTH; + globals.write_csv = ODBC_CDR_CSV_NEVER; + + if ((settings = switch_xml_child(cfg, "settings")) != NULL) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (zstr(var) || zstr(val)) { + continue; // Ignore empty entries + } + if (!strcasecmp(var, "dbname")) { + globals.dbname = strdup(val); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set dbname [%s]\n", globals.dbname); + } else if (!strcasecmp(var, "odbc-dsn")) { + globals.odbc_dsn = strdup(val); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set odbc-dsn [%s]\n", globals.odbc_dsn); + } else if (!strcasecmp(var, "log-leg")) { + if (!strcasecmp(val, "a-leg")) { + globals.log_leg = ODBC_CDR_LOG_A; + } else if (!strcasecmp(val, "b-leg")) { + globals.log_leg = ODBC_CDR_LOG_B; + } + } else if (!strcasecmp(var, "debug-sql") && switch_true(val)) { + globals.debug_sql = SWITCH_TRUE; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set debug-sql [true]\n"); + } else if (!strcasecmp(var, "write-csv") && !zstr(val)) { + if (!strcasecmp(val, "always")) { + globals.write_csv = ODBC_CDR_CSV_ALWAYS; + } else if (!strcasecmp(val, "on-db-fail")) { + globals.write_csv = ODBC_CDR_CSV_ON_FAIL; + } + } else if (!strcasecmp(var, "csv-path") && !zstr(val)) { + globals.csv_path = switch_mprintf("%s%s", val, SWITCH_PATH_SEPARATOR); + } else if (!strcasecmp(var, "csv-path-on-fail") && !zstr(val)) { + globals.csv_fail_path = switch_mprintf("%s%s", val, SWITCH_PATH_SEPARATOR); + } + } + } + + if (globals.log_leg == ODBC_CDR_LOG_A) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [a-leg]\n"); + } else if (globals.log_leg == ODBC_CDR_LOG_B) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [b-leg]\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set log-leg [both]\n"); + } + + if (!globals.csv_path) { + globals.csv_path = switch_mprintf("%s%sodbc-cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR); + } + + if (!globals.csv_fail_path) { + globals.csv_fail_path = switch_mprintf("%s%sodbc-cdr-failed", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set csv-path [%s]\n", globals.csv_path); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set csv-path-on-fail [%s]\n", globals.csv_fail_path); + + if ((tables = switch_xml_child(cfg, "tables"))) { + for (table = switch_xml_child(tables, "table"); table; table = table->next) { + load_table(switch_xml_attr_soft(table, "name")); + } + } + + if (!globals.dbname) { + globals.dbname = strdup(ODBC_CDR_SQLITE_DB_NAME); + } + + // Initialize database + if (!(dbh = get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot open DB!\n"); + status = SWITCH_STATUS_TERM; + goto end; + } + + switch_cache_db_release_db_handle(&dbh); + +end: + switch_mutex_unlock(globals.mutex); + + if (xml) { + switch_xml_free(xml); + } + + return status; +} + + +SWITCH_MODULE_LOAD_FUNCTION(mod_odbc_cdr_load) +{ + switch_status_t status; + + memset(&globals, 0, sizeof(globals)); + switch_core_hash_init(&globals.table_hash); + if (switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to initialize mutex\n"); + } + globals.pool = pool; + + if ((status = odbc_cdr_load_config()) != SWITCH_STATUS_SUCCESS) { + return status; + } + + if (globals.write_csv != ODBC_CDR_CSV_NEVER) { + if ((status = switch_dir_make_recursive(globals.csv_path, SWITCH_DEFAULT_DIR_PERMS, pool)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s\n", globals.csv_path); + return status; + } + if (strcasecmp(globals.csv_path, globals.csv_fail_path)) { + if ((status = switch_dir_make_recursive(globals.csv_fail_path, SWITCH_DEFAULT_DIR_PERMS, pool)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s\n", globals.csv_path); + return status; + } + } + } + + switch_mutex_lock(globals.mutex); + globals.running = 1; + switch_mutex_unlock(globals.mutex); + + switch_core_add_state_handler(&odbc_cdr_state_handlers); + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_odbc_cdr_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_odbc_cdr_shutdown) +{ + switch_hash_index_t *hi = NULL; + table_profile_t *table; + void *val = NULL; + const void *key; + switch_ssize_t keylen; + + switch_mutex_lock(globals.mutex); + if (globals.running == 1) { + globals.running = 0; + } + + while ((hi = switch_core_hash_first_iter(globals.table_hash, hi))) { + switch_hash_index_t *field_hi = NULL; + void *field_val = NULL; + const void *field_key; + switch_ssize_t field_keylen; + + switch_core_hash_this(hi, &key, &keylen, &val); + table = (table_profile_t *) val; + + while ((field_hi = switch_core_hash_first_iter(table->field_hash, field_hi))) { + switch_core_hash_this(field_hi, &field_key, &field_keylen, &field_val); + switch_core_hash_delete_locked(table->field_hash, field_key, table->mutex); + } + switch_core_hash_destroy(&table->field_hash); + switch_safe_free(field_hi); + + switch_core_hash_delete(globals.table_hash, table->name); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying table %s\n", table->name); + + switch_core_destroy_memory_pool(&table->pool); + table = NULL; + } + switch_core_hash_destroy(&globals.table_hash); + switch_safe_free(hi); + + switch_safe_free(globals.csv_path) + switch_safe_free(globals.csv_fail_path) + switch_safe_free(globals.odbc_dsn); + switch_safe_free(globals.dbname); + + switch_mutex_unlock(globals.mutex); + switch_mutex_destroy(globals.mutex); + + switch_core_remove_state_handler(&odbc_cdr_state_handlers); + + return SWITCH_STATUS_SUCCESS; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ diff --git a/src/switch_utils.c b/src/switch_utils.c index 3cde97a127..444333ec12 100644 --- a/src/switch_utils.c +++ b/src/switch_utils.c @@ -2567,6 +2567,12 @@ SWITCH_DECLARE(int) switch_wait_sock(switch_os_socket_t sock, uint32_t ms, switc s = poll(pfds, 1, ms); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2645,6 +2651,12 @@ SWITCH_DECLARE(int) switch_wait_socklist(switch_waitlist_t *waitlist, uint32_t l s = poll(pfds, len, ms); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2758,6 +2770,12 @@ SWITCH_DECLARE(int) switch_wait_sock(switch_os_socket_t sock, uint32_t ms, switc s = select(sock + 1, (flags & SWITCH_POLL_READ) ? rfds : NULL, (flags & SWITCH_POLL_WRITE) ? wfds : NULL, (flags & SWITCH_POLL_ERROR) ? efds : NULL, &tv); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) { @@ -2858,6 +2876,12 @@ SWITCH_DECLARE(int) switch_wait_socklist(switch_waitlist_t *waitlist, uint32_t l s = select(max_fd + 1, (flags & SWITCH_POLL_READ) ? rfds : NULL, (flags & SWITCH_POLL_WRITE) ? wfds : NULL, (flags & SWITCH_POLL_ERROR) ? efds : NULL, &tv); + if (s < 0) { + if (switch_errno_is_break(switch_errno())) { + s = 0; + } + } + if (s < 0) { r = s; } else if (s > 0) {