asterisk-chan-dongle/chan_dongle.c

1882 lines
44 KiB
C

/*
* chan_dongle
*
* Copyright (C) 2011-2015
* bg <bg_one@mail.ru>
* http://www.e1550.mobi
*
* chan_dongle is based on chan_datacard by
*
* Artem Makhutov <artem@makhutov.org>
* http://www.makhutov.org
*
* Dmitry Vagin <dmitry2004@yandex.ru>
*
* chan_datacard is based on chan_mobile by Digium
* (Mark Spencer <markster@digium.com>)
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief UMTS Voice Dongle channel driver
*
* \author Artem Makhutov <artem@makhutov.org>
* \author Dave Bowerman <david.bowerman@gmail.com>
* \author Dmitry Vagin <dmitry2004@yandex.ru>
* \author bg <bg_one@mail.ru>
* \author Max von Buelow <max@m9x.de>
*
* \ingroup channel_drivers
*/
#include "ast_config.h"
#if ASTERISK_VERSION_NUM < 140000 /* 14- */
ASTERISK_FILE_VERSION(__FILE__, "$Rev: " PACKAGE_REVISION " $")
#endif /* 14- */
#include <asterisk/stringfields.h> /* AST_DECLARE_STRING_FIELDS for asterisk/manager.h */
#include <asterisk/manager.h>
#include <asterisk/dsp.h>
#include <asterisk/callerid.h>
#include <asterisk/module.h> /* AST_MODULE_LOAD_DECLINE ... */
#include <asterisk/timing.h> /* ast_timer_open() ast_timer_fd() */
#include <sys/stat.h> /* S_IRUSR | S_IRGRP | S_IROTH */
#include <termios.h> /* struct termios tcgetattr() tcsetattr() */
#include <pthread.h> /* pthread_t pthread_kill() pthread_join() */
#include <fcntl.h> /* O_RDWR O_NOCTTY */
#include <signal.h> /* SIGURG */
#include "ast_compat.h" /* asterisk compatibility fixes */
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
#include <asterisk/stasis_channels.h>
#include <asterisk/format_cache.h>
#endif /* ^13+ */
#include "chan_dongle.h"
#include "at_response.h" /* at_res_t */
#include "at_queue.h" /* struct at_queue_task_cmd at_queue_head_cmd() */
#include "at_command.h" /* at_cmd2str() */
#include "mutils.h" /* ITEMS_OF() */
#include "at_read.h"
#include "cli.h"
#include "app.h"
#include "manager.h"
#include "channel.h" /* channel_queue_hangup() */
#include "dc_config.h" /* dc_uconfig_fill() dc_gconfig_fill() dc_sconfig_fill() */
#include "pdiscovery.h" /* pdiscovery_lookup() pdiscovery_init() pdiscovery_fini() */
#include "smsdb.h"
#include "error.h"
EXPORT_DEF const char * const dev_state_strs[4] = { "stop", "restart", "remove", "start" };
EXPORT_DEF public_state_t * gpublic;
#if ASTERISK_VERSION_NUM >= 100000 && ASTERISK_VERSION_NUM < 130000 /* 10-13 */
EXPORT_DEF struct ast_format chan_dongle_format;
EXPORT_DEF struct ast_format_cap * chan_dongle_format_cap;
#endif /* ^10-13 */
static int public_state_init(struct public_state * state);
/*!
* Get status of the dongle. It might happen that the device disappears
* (e.g. due to a USB unplug).
*
* \return 0 if device seems ok, non-0 if it seems not available
*/
static int port_status (int fd)
{
struct termios t;
if (fd < 0)
{
return -1;
}
return tcgetattr (fd, &t);
}
#/* return length of lockname */
static int lock_build(const char * devname, char * buf, unsigned length)
{
const char * basename;
char resolved_path[PATH_MAX];
/* follow symlinks */
if(realpath(devname, resolved_path) != NULL)
devname = resolved_path;
basename = strrchr(devname, '/');
if(basename)
basename++;
else
basename = devname;
/* NOTE: use system system wide lock directory */
#if defined(__FreeBSD__)
return snprintf(buf, length, "/var/spool/lock/LCK..%s", basename);
#else
return snprintf(buf, length, "/var/lock/LCK..%s", basename);
#endif
}
#/* return 0 on error */
static int lock_create(const char * lockfile)
{
int fd;
int len = 0;
char pidb[21];
fd = open(lockfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH);
if(fd >= 0)
{
/* NOTE: bg: i assume next open reuse same fd - not thread-safe */
len = snprintf(pidb, sizeof(pidb), "%d %d", getpid(), fd);
len = write(fd, pidb, len);
close(fd);
} else {
ast_log(LOG_ERROR, "open('%s') failed: %s\n", lockfile, strerror(errno));
}
return len;
}
#/* return pid of owner, 0 if free */
EXPORT_DEF int lock_try(const char * devname, char ** lockname)
{
int fd;
int len;
int pid = 0;
int assigned;
int fd2;
char name[1024];
char buffer[65];
lock_build(devname, name, sizeof(name));
/* FIXME: rise conditions: some time between lock check and got lock */
fd = open(name, O_RDONLY);
if(fd >= 0)
{
len = read(fd, buffer, sizeof(buffer) - 1);
if(len > 0)
{
buffer[len] = 0;
assigned = sscanf(buffer, "%d %d", &len, &fd2);
if(assigned > 0 && kill(len, 0) == 0)
{
if(len == getpid() && assigned > 1)
{
if(port_status(fd2) == 0)
pid = len;
}
else
pid = len;
}
}
close(fd);
}
if(pid == 0)
{
unlink(name);
lock_create(name);
*lockname = ast_strdup(name);
}
return pid;
}
#/* */
EXPORT_DEF void closetty(int fd, char ** lockfname)
{
close(fd);
/* remove lock */
unlink(*lockfname);
ast_free(*lockfname);
*lockfname = NULL;
}
EXPORT_DEF int opentty (const char* dev, char ** lockfile)
{
int flags;
int pid;
int fd;
struct termios term_attr;
char buf[40];
pid = lock_try(dev, lockfile);
if(pid != 0)
{
ast_log (LOG_WARNING, "%s already used by process %d\n", dev, pid);
return -1;
}
fd = open(dev, O_RDWR | O_NOCTTY);
if (fd < 0)
{
flags = errno;
closetty(fd, lockfile);
snprintf(buf, sizeof(buf), "Open Failed\r\nErrorCode: %d", flags);
manager_event_message_raw("DonglePortFail", dev, buf);
ast_log (LOG_WARNING, "unable to open %s: %s\n", dev, strerror(flags));
return -1;
}
/* Put the terminal into exclusive mode. All other open(2)s by
* non-root will fail with EBUSY. */
if (ioctl(fd, TIOCEXCL) != 0) {
ast_log(LOG_WARNING, "ioctl(TIOCEXCL) failed for %s: %s\n", dev, strerror(errno));
}
flags = fcntl(fd, F_GETFD);
if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
{
flags = errno;
closetty(fd, lockfile);
ast_log (LOG_WARNING, "fcntl(F_GETFD/F_SETFD) failed for %s: %s\n", dev, strerror(flags));
return -1;
}
if (tcgetattr (fd, &term_attr) != 0)
{
flags = errno;
closetty(fd, lockfile);
ast_log (LOG_WARNING, "tcgetattr() failed for %s: %s\n", dev, strerror(flags));
return -1;
}
term_attr.c_cflag = B115200 | CS8 | CREAD | CRTSCTS;
term_attr.c_iflag = 0;
term_attr.c_oflag = 0;
term_attr.c_lflag = 0;
term_attr.c_cc[VMIN] = 1;
term_attr.c_cc[VTIME] = 0;
if (tcsetattr (fd, TCSAFLUSH, &term_attr) != 0)
{
ast_log (LOG_WARNING, "tcsetattr(TCSAFLUSH) failed for %s: %s\n", dev, strerror(errno));
}
return fd;
}
#/* phone monitor thread pvt cleanup */
static void disconnect_dongle (struct pvt* pvt)
{
struct cpvt * cpvt, * next;
if (!PVT_NO_CHANS(pvt))
{
ast_debug (1, "[%s] Dongle disconnecting, hanging up channels\n", PVT_ID(pvt));
for(cpvt = pvt->chans.first; cpvt; cpvt = next)
{
next = cpvt->entry.next;
at_hangup_immediality(cpvt);
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP);
change_channel_state(cpvt, CALL_STATE_RELEASED, 0);
}
}
at_queue_flush(pvt);
pvt->last_dialed_cpvt = NULL;
closetty (pvt->audio_fd, &pvt->alock);
closetty (pvt->data_fd, &pvt->dlock);
pvt->data_fd = -1;
pvt->audio_fd = -1;
if(pvt->dsp)
ast_dsp_digitreset(pvt->dsp);
pvt_on_remove_last_channel(pvt);
/* pvt->a_write_rb */
pvt->dtmf_digit = 0;
pvt->rings = 0;
// else
{
/* unaffected in case of restart */
pvt->gsm_reg_status = -1;
pvt->rssi = 0;
pvt->linkmode = 0;
pvt->linksubmode = 0;
ast_copy_string (pvt->provider_name, "NONE", sizeof (pvt->provider_name));
pvt->manufacturer[0] = '\0';
pvt->model[0] = '\0';
pvt->firmware[0] = '\0';
pvt->imei[0] = '\0';
pvt->imsi[0] = '\0';
pvt->has_subscriber_number = 0;
ast_copy_string (pvt->subscriber_number, "Unknown", sizeof (pvt->subscriber_number));
pvt->location_area_code[0] = '\0';
pvt->cell_id[0] = '\0';
pvt->sms_scenter[0] = '\0';
pvt->gsm_registered = 0;
pvt->has_sms = 0;
pvt->has_voice = 0;
pvt->has_call_waiting = 0;
}
pvt->connected = 0;
pvt->initialized = 0;
pvt->has_call_waiting = 0;
/* FIXME: LOST real device state */
pvt->dialing = 0;
pvt->ring = 0;
pvt->cwaiting = 0;
pvt->outgoing_sms = 0;
pvt->incoming_sms_index = -1U;
pvt->volume_sync_step = VOLUME_SYNC_BEGIN;
pvt->current_state = DEV_STATE_STOPPED;
/* clear statictics */
memset(&pvt->stat, 0, sizeof(pvt->stat));
ast_copy_string (PVT_STATE(pvt, data_tty), CONF_UNIQ(pvt, data_tty), sizeof (PVT_STATE(pvt, data_tty)));
ast_copy_string (PVT_STATE(pvt, audio_tty), CONF_UNIQ(pvt, audio_tty), sizeof (PVT_STATE(pvt, audio_tty)));
ast_verb (3, "[%s] Dongle has disconnected\n", PVT_ID(pvt));
manager_event_device_status(PVT_ID(pvt), "Disconnect");
}
#define SMS_INBOX_BIT(index) ((sms_inbox_item_type)(1) << (index % SMS_INBOX_ITEM_BITS))
#define SMS_INBOX_INDEX(index) (index / SMS_INBOX_ITEM_BITS)
static int is_sms_inbox_index_valid(const struct pvt* pvt, int index)
{
if (index < 0 || index >= (int) SMS_INDEX_MAX) {
ast_log(LOG_WARNING, "[%s] SMS index [%d] out of range\n", PVT_ID(pvt), index);
return 0;
}
return 1;
}
EXPORT_DEF int sms_inbox_set(struct pvt* pvt, int index)
{
if (!is_sms_inbox_index_valid(pvt, index)) {
return 0;
}
pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] |= SMS_INBOX_BIT(index);
return 1;
}
EXPORT_DEF int sms_inbox_clear(struct pvt* pvt, int index)
{
if (!is_sms_inbox_index_valid(pvt, index)) {
return 0;
}
pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] &= ~SMS_INBOX_BIT(index);
return 1;
}
EXPORT_DEF int is_sms_inbox_set(const struct pvt* pvt, int index)
{
if (!is_sms_inbox_index_valid(pvt, index)) {
return 0;
}
return pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] & SMS_INBOX_BIT(index);
}
#undef SMS_INBOX_INDEX
#undef SMS_INBOX_BIT
/* anybody wrote some to device before me, and not read results, clean pending results here */
#/* */
EXPORT_DEF void clean_read_data(const char * devname, int fd)
{
char buf[2*1024];
struct ringbuffer rb;
int iovcnt;
int t;
rb_init (&rb, buf, sizeof (buf));
for (t = 0; at_wait(fd, &t); t = 0)
{
iovcnt = at_read (fd, devname, &rb);
ast_debug (4, "[%s] drop %u bytes of pending data before initialization\n", devname, (unsigned)rb_used(&rb));
/* drop read */
rb_init (&rb, buf, sizeof (buf));
if (iovcnt == 0)
break;
}
}
static void handle_expired_reports(struct pvt *pvt)
{
char dst[SMSDB_DST_MAX_LEN];
char payload[SMSDB_PAYLOAD_MAX_LEN];
ssize_t payload_len = smsdb_outgoing_purge_one(dst, payload);
if (payload_len >= 0) {
ast_verb (3, "[%s] TTL payload: %.*s\n", PVT_ID(pvt), (int) payload_len, payload);
channel_var_t vars[] =
{
{ "SMS_REPORT_PAYLOAD", payload },
{ "SMS_REPORT_TS", "" },
{ "SMS_REPORT_DT", "" },
{ "SMS_REPORT_SUCCESS", "0" },
{ "SMS_REPORT_TYPE", "t" },
{ "SMS_REPORT", "" },
{ NULL, NULL },
};
start_local_channel(pvt, "report", dst, vars);
manager_event_report(PVT_ID(pvt), payload, payload_len, "", "", 0, 2, "");
}
}
/*!
* \brief Check if the module is unloading.
* \retval 0 not unloading
* \retval 1 unloading
*/
static void* do_monitor_phone (void* data)
{
struct pvt* pvt = (struct pvt*) data;
at_res_t at_res;
const struct at_queue_cmd * ecmd;
int t;
char buf[2*1024];
struct ringbuffer rb;
struct iovec iov[2];
int iovcnt;
char dev[sizeof(PVT_ID(pvt))];
int fd;
int read_result = 0;
pvt->timeout = DATA_READ_TIMEOUT;
rb_init (&rb, buf, sizeof (buf));
ast_mutex_lock (&pvt->lock);
/* 4 reduce locking time make copy of this readonly fields */
fd = pvt->data_fd;
ast_copy_string(dev, PVT_ID(pvt), sizeof(dev));
clean_read_data(dev, fd);
/* schedule dongle initilization */
if (at_enqueue_initialization(&pvt->sys_chan, CMD_AT))
{
ast_log (LOG_ERROR, "[%s] Error adding initialization commands to queue\n", dev);
goto e_cleanup;
}
/* Poll first SMS, if any */
if (at_poll_sms(pvt) == 0)
{
ast_debug (1, "[%s] Polling first SMS message\n", PVT_ID(pvt));
}
ast_mutex_unlock (&pvt->lock);
while (1)
{
ast_mutex_lock (&pvt->lock);
handle_expired_reports(pvt);
if (port_status (pvt->data_fd) || port_status (pvt->audio_fd))
{
ast_log (LOG_ERROR, "[%s] Lost connection to Dongle\n", dev);
goto e_cleanup;
}
if(pvt->terminate_monitor)
{
ast_log (LOG_NOTICE, "[%s] stopping by %s request\n", dev, dev_state2str(pvt->desired_state));
goto e_restart;
}
t = at_queue_timeout(pvt);
if(t < 0)
t = pvt->timeout;
ast_mutex_unlock (&pvt->lock);
if (!at_wait (fd, &t))
{
ast_mutex_lock (&pvt->lock);
ecmd = at_queue_head_cmd (pvt);
if(ecmd)
{
ast_log (LOG_ERROR, "[%s] timedout while waiting '%s' in response to '%s'\n", dev, at_res2str (ecmd->res), at_cmd2str (ecmd->cmd));
goto e_cleanup;
}
at_enqueue_ping(&pvt->sys_chan);
ast_mutex_unlock (&pvt->lock);
continue;
}
/* FIXME: access to device not locked */
iovcnt = at_read (fd, dev, &rb);
if (iovcnt < 0)
{
break;
}
ast_mutex_lock (&pvt->lock);
PVT_STAT(pvt, d_read_bytes) += iovcnt;
ast_mutex_unlock (&pvt->lock);
while ((iovcnt = at_read_result_iov (dev, &read_result, &rb, iov)) > 0)
{
at_res = at_read_result_classification (&rb, iov[0].iov_len + iov[1].iov_len);
ast_mutex_lock (&pvt->lock);
PVT_STAT(pvt, at_responses) ++;
if (at_response (pvt, iov, iovcnt, at_res) || at_queue_run(pvt))
{
goto e_cleanup;
}
ast_mutex_unlock (&pvt->lock);
}
}
ast_mutex_lock (&pvt->lock);
e_cleanup:
if (!pvt->initialized)
{
// TODO: send monitor event
ast_verb (3, "[%s] Error initializing Dongle\n", dev);
}
/* it real, unsolicited disconnect */
pvt->terminate_monitor = 0;
e_restart:
disconnect_dongle (pvt);
// pvt->monitor_running = 0;
ast_mutex_unlock (&pvt->lock);
/* TODO: wakeup discovery thread after some delay */
return NULL;
}
static inline int start_monitor (struct pvt * pvt)
{
if (ast_pthread_create_background (&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0)
{
pvt->monitor_thread = AST_PTHREADT_NULL;
return 0;
}
return 1;
}
#/* */
static void pvt_stop(struct pvt * pvt)
{
pthread_t id;
if(pvt->monitor_thread != AST_PTHREADT_NULL)
{
pvt->terminate_monitor = 1;
pthread_kill (pvt->monitor_thread, SIGURG);
id = pvt->monitor_thread;
ast_mutex_unlock (&pvt->lock);
pthread_join (id, NULL);
ast_mutex_lock (&pvt->lock);
pvt->terminate_monitor = 0;
pvt->monitor_thread = AST_PTHREADT_NULL;
}
// pvt->current_state = DEV_STATE_STOPPED;
}
#/* called with pvt lock hold */
static int pvt_discovery(struct pvt * pvt)
{
char devname[DEVNAMELEN];
char imei[IMEI_SIZE+1];
char imsi[IMSI_SIZE+1];
int resolved;
if(CONF_UNIQ(pvt, data_tty)[0] == 0 && CONF_UNIQ(pvt, audio_tty)[0] == 0) {
char * data_tty;
char * audio_tty;
ast_copy_string(devname, PVT_ID(pvt), sizeof(devname));
ast_copy_string(imei, CONF_UNIQ(pvt, imei), sizeof(imei));
ast_copy_string(imsi, CONF_UNIQ(pvt, imsi), sizeof(imsi));
ast_debug(3, "[%s] Trying ports discovery for%s%s%s%s\n",
PVT_ID(pvt),
imei[0] == 0 ? "" : " IMEI ",
imei,
imsi[0] == 0 ? "" : " IMSI ",
imsi
);
ast_mutex_unlock (&pvt->lock);
//sleep(10); // test
resolved = pdiscovery_lookup(devname, imei, imsi, &data_tty, &audio_tty);
ast_mutex_lock (&pvt->lock);
if(resolved) {
ast_copy_string (PVT_STATE(pvt, data_tty), data_tty, sizeof (PVT_STATE(pvt, data_tty)));
ast_copy_string (PVT_STATE(pvt, audio_tty), audio_tty, sizeof (PVT_STATE(pvt, audio_tty)));
ast_free(audio_tty);
ast_free(data_tty);
ast_verb (3, "[%s]%s%s%s%s found on data_tty=%s audio_tty=%s\n",
PVT_ID(pvt),
imei[0] == 0 ? "" : " IMEI ",
imei,
imsi[0] == 0 ? "" : " IMSI ",
imsi,
PVT_STATE(pvt, data_tty),
PVT_STATE(pvt, audio_tty)
);
} else {
ast_debug(3, "[%s] Not found ports for%s%s%s%s\n",
PVT_ID(pvt),
imei[0] == 0 ? "" : " IMEI ",
imei,
imsi[0] == 0 ? "" : " IMSI ",
imsi
);
}
} else {
ast_copy_string (PVT_STATE(pvt, data_tty), CONF_UNIQ(pvt, data_tty), sizeof (PVT_STATE(pvt, data_tty)));
ast_copy_string (PVT_STATE(pvt, audio_tty), CONF_UNIQ(pvt, audio_tty), sizeof (PVT_STATE(pvt, audio_tty)));
resolved = 1;
}
return ! resolved;
}
#/* */
static void pvt_start(struct pvt * pvt)
{
long flags;
/* prevent start_monitor() multiple times and on turned off devices */
if (pvt->connected || pvt->desired_state != DEV_STATE_STARTED) {
// || (pvt->monitor_thread != AST_PTHREADT_NULL &&
// (pthread_kill(pvt->monitor_thread, 0) == 0 || errno != ESRCH))
return;
}
pvt_stop(pvt);
if (pvt_discovery(pvt)) {
return;
}
ast_verb(3, "[%s] Trying to connect on %s...\n", PVT_ID(pvt), PVT_STATE(pvt, data_tty));
pvt->data_fd = opentty(PVT_STATE(pvt, data_tty), &pvt->dlock);
if (pvt->data_fd < 0) {
return;
}
// TODO: delay until device activate voice call or at pvt_on_create_1st_channel()
pvt->audio_fd = opentty(PVT_STATE(pvt, audio_tty), &pvt->alock);
if (pvt->audio_fd < 0) {
goto cleanup_datafd;
}
if (!start_monitor(pvt)) {
goto cleanup_audiofd;
}
/* Set data_fd and audio_fd to non-blocking. This appears to fix
* incidental deadlocks occurring with Asterisk 12+ or with
* jitterbuffer enabled. Apparently Asterisk can call the
* (audio) read function for sockets that don't have data to
* read(). */
flags = fcntl(pvt->data_fd, F_GETFL);
fcntl(pvt->data_fd, F_SETFL, flags | O_NONBLOCK);
flags = fcntl(pvt->audio_fd, F_GETFL);
fcntl(pvt->audio_fd, F_SETFL, flags | O_NONBLOCK);
pvt->connected = 1;
pvt->current_state = DEV_STATE_STARTED;
manager_event_device_status(PVT_ID(pvt), "Connect");
ast_verb(3, "[%s] Dongle has connected, initializing...\n", PVT_ID(pvt));
return;
cleanup_audiofd:
closetty(pvt->audio_fd, &pvt->alock);
cleanup_datafd:
closetty(pvt->data_fd, &pvt->dlock);
}
#/* */
static void pvt_free(struct pvt * pvt)
{
at_queue_flush(pvt);
if(pvt->dsp)
ast_dsp_free(pvt->dsp);
ast_mutex_unlock(&pvt->lock);
ast_free(pvt);
}
#/* */
static void pvt_destroy(struct pvt * pvt)
{
ast_mutex_lock(&pvt->lock);
pvt_stop(pvt);
pvt_free(pvt);
}
static void * do_discovery(void * arg)
{
struct public_state * state = (struct public_state *) arg;
struct pvt * pvt;
while(state->unloading_flag == 0)
{
/* read lock for avoid deadlock when IMEI/IMSI discovery */
AST_RWLIST_RDLOCK(&state->devices);
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
pvt->must_remove = 0;
if(pvt->restart_time == RESTATE_TIME_NOW && pvt->desired_state != pvt->current_state)
{
switch(pvt->desired_state)
{
case DEV_STATE_RESTARTED:
pvt_stop(pvt);
pvt->desired_state = DEV_STATE_STARTED;
/* fall through */
case DEV_STATE_STARTED:
pvt_start(pvt);
break;
case DEV_STATE_REMOVED:
pvt_stop(pvt);
pvt->must_remove = 1;
break;
case DEV_STATE_STOPPED:
pvt_stop(pvt);
}
}
ast_mutex_unlock (&pvt->lock);
}
AST_RWLIST_UNLOCK (&state->devices);
/* actual device removal here for avoid long (discovery) time write lock on device list in loop above */
AST_RWLIST_WRLOCK(&state->devices);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&state->devices, pvt, entry)
{
ast_mutex_lock(&pvt->lock);
if(pvt->must_remove)
{
AST_RWLIST_REMOVE_CURRENT(entry);
pvt_free(pvt);
} else
ast_mutex_unlock(&pvt->lock);
}
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&state->devices);
/* Go to sleep (only if we are not unloading) */
if (state->unloading_flag == 0)
{
sleep(SCONF_GLOBAL(state, discovery_interval));
}
}
return NULL;
}
#/* */
static int discovery_restart(public_state_t * state)
{
if(state->discovery_thread == AST_PTHREADT_STOP)
return 0;
ast_mutex_lock(&state->discovery_lock);
if (state->discovery_thread == pthread_self()) {
ast_mutex_unlock(&state->discovery_lock);
ast_log(LOG_WARNING, "Cannot kill myself\n");
return -1;
}
if (state->discovery_thread != AST_PTHREADT_NULL) {
/* Wake up the thread */
pthread_kill(state->discovery_thread, SIGURG);
} else {
/* Start a new monitor */
if (ast_pthread_create_background(&state->discovery_thread, NULL, do_discovery, state) < 0) {
ast_mutex_unlock(&state->discovery_lock);
ast_log(LOG_ERROR, "Unable to start discovery thread\n");
return -1;
}
}
ast_mutex_unlock(&state->discovery_lock);
return 0;
}
#/* */
static void discovery_stop(public_state_t * state)
{
/* signal for discovery unloading */
state->unloading_flag = 1;
ast_mutex_lock(&state->discovery_lock);
if (state->discovery_thread && (state->discovery_thread != AST_PTHREADT_STOP) && (state->discovery_thread != AST_PTHREADT_NULL)) {
// pthread_cancel(state->discovery_thread);
pthread_kill(state->discovery_thread, SIGURG);
pthread_join(state->discovery_thread, NULL);
}
state->discovery_thread = AST_PTHREADT_STOP;
ast_mutex_unlock(&state->discovery_lock);
}
#/* */
EXPORT_DEF void pvt_on_create_1st_channel(struct pvt* pvt)
{
mixb_init (&pvt->a_write_mixb, pvt->a_write_buf, sizeof (pvt->a_write_buf));
// rb_init (&pvt->a_write_rb, pvt->a_write_buf, sizeof (pvt->a_write_buf));
if(!pvt->a_timer)
pvt->a_timer = ast_timer_open ();
/* FIXME: do on each channel switch */
if(pvt->dsp)
ast_dsp_digitreset (pvt->dsp);
pvt->dtmf_digit = 0;
pvt->dtmf_begin_time.tv_sec = 0;
pvt->dtmf_begin_time.tv_usec = 0;
pvt->dtmf_end_time.tv_sec = 0;
pvt->dtmf_end_time.tv_usec = 0;
manager_event_device_status(PVT_ID(pvt), "Used");
}
#/* */
EXPORT_DEF void pvt_on_remove_last_channel(struct pvt* pvt)
{
if (pvt->a_timer)
{
ast_timer_close(pvt->a_timer);
pvt->a_timer = NULL;
}
manager_event_device_status(PVT_ID(pvt), "Free");
}
#define SET_BIT(dw_array,bitno) do { (dw_array)[(bitno) >> 5] |= 1 << ((bitno) & 31) ; } while(0)
#define TEST_BIT(dw_array,bitno) ((dw_array)[(bitno) >> 5] & 1 << ((bitno) & 31))
#/* */
EXPORT_DEF int pvt_get_pseudo_call_idx(const struct pvt * pvt)
{
struct cpvt * cpvt;
int * bits;
int dwords = ((MAX_CALL_IDX + sizeof(*bits) - 1) / sizeof(*bits));
bits = alloca(dwords * sizeof(*bits));
memset(bits, 0, dwords * sizeof(*bits));
AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) {
SET_BIT(bits, cpvt->call_idx);
}
for(dwords = 1; dwords <= MAX_CALL_IDX; dwords++)
{
if(!TEST_BIT(bits, dwords))
return dwords;
}
return 0;
}
#undef TEST_BIT
#undef SET_BIT
#/* */
static int is_dial_possible2(const struct pvt * pvt, int opts, const struct cpvt * ignore_cpvt)
{
struct cpvt * cpvt;
int hold = 0;
int active = 0;
// FIXME: allow HOLD states for CONFERENCE
int use_call_waiting = opts & CALL_FLAG_HOLD_OTHER;
if(pvt->ring || pvt->cwaiting || pvt->dialing)
return 0;
AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) {
switch(cpvt->state)
{
case CALL_STATE_INIT:
if(cpvt != ignore_cpvt)
return 0;
break;
case CALL_STATE_DIALING:
case CALL_STATE_ALERTING:
case CALL_STATE_INCOMING:
case CALL_STATE_WAITING:
return 0;
case CALL_STATE_ACTIVE:
if(hold || !use_call_waiting)
return 0;
active++;
break;
case CALL_STATE_ONHOLD:
if(active || !use_call_waiting)
return 0;
hold++;
break;
case CALL_STATE_RELEASED:
;
}
}
return 1;
}
#/* */
EXPORT_DEF int is_dial_possible(const struct pvt * pvt, int opts)
{
return is_dial_possible2(pvt, opts, NULL);
}
#/* */
EXPORT_DECL int pvt_enabled(const struct pvt * pvt)
{
return pvt->current_state == DEV_STATE_STARTED && (pvt->desired_state == pvt->current_state || pvt->restart_time == RESTATE_TIME_CONVENIENT);
}
#/* */
EXPORT_DEF int ready4voice_call(const struct pvt* pvt, const struct cpvt * current_cpvt, int opts)
{
if(!pvt->connected
|| !pvt->initialized
|| !pvt->has_voice
|| !pvt->gsm_registered
|| !pvt_enabled(pvt)) {
return 0;
}
return is_dial_possible2(pvt, opts, current_cpvt);
}
#/* */
static int can_dial(struct pvt* pvt, int opts, const struct ast_channel * requestor)
{
/* not allow hold requester channel :) */
/* FIXME: requestor may be just proxy/masquerade for real channel */
// use ast_bridged_channel(chan) ?
// use requestor->tech->get_base_channel() ?
if((opts & CALL_FLAG_HOLD_OTHER) == CALL_FLAG_HOLD_OTHER && channels_loop(pvt, requestor))
return 0;
return ready4voice_call(pvt, NULL, opts);
}
#/* return locked pvt or NULL */
EXPORT_DEF struct pvt * find_device_ex(struct public_state * state, const char * name)
{
struct pvt * pvt;
AST_RWLIST_RDLOCK(&state->devices);
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (!strcmp (PVT_ID(pvt), name))
{
break;
}
ast_mutex_unlock (&pvt->lock);
}
AST_RWLIST_UNLOCK(&state->devices);
return pvt;
}
#/* return locked pvt or NULL */
EXPORT_DEF struct pvt * find_device_ext (const char * name)
{
struct pvt * pvt = find_device(name);
if (pvt) {
if (!pvt_enabled(pvt)) {
ast_mutex_unlock (&pvt->lock);
chan_dongle_err = E_DEVICE_DISABLED;
pvt = NULL;
}
} else {
chan_dongle_err = E_DEVICE_NOT_FOUND;
}
return pvt;
}
#/* like find_device but for resource spec; return locked! pvt or NULL */
EXPORT_DEF struct pvt * find_device_by_resource_ex(struct public_state * state, const char * resource, int opts, const struct ast_channel * requestor, int * exists)
{
int group;
size_t i;
size_t j;
size_t c;
size_t last_used;
struct pvt * pvt;
struct pvt * found = NULL;
struct pvt * round_robin[MAXDONGLEDEVICES];
*exists = 0;
/* Find requested device and make sure it's connected and initialized. */
AST_RWLIST_RDLOCK(&state->devices);
if (((resource[0] == 'g') || (resource[0] == 'G')) && ((resource[1] >= '0') && (resource[1] <= '9')))
{
errno = 0;
group = (int) strtol (&resource[1], (char**) NULL, 10);
if (errno != EINVAL)
{
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (CONF_SHARED(pvt, group) == group)
{
*exists = 1;
if(can_dial(pvt, opts, requestor))
{
found = pvt;
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
}
}
else if (((resource[0] == 'r') || (resource[0] == 'R')) && ((resource[1] >= '0') && (resource[1] <= '9')))
{
errno = 0;
group = (int) strtol (&resource[1], (char**) NULL, 10);
if (errno != EINVAL)
{
/* Generate a list of all available devices */
j = ITEMS_OF (round_robin);
c = 0; last_used = 0;
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (CONF_SHARED(pvt, group) == group)
{
round_robin[c] = pvt;
if (pvt->group_last_used == 1)
{
pvt->group_last_used = 0;
last_used = c;
}
c++;
if (c == j)
{
ast_mutex_unlock (&pvt->lock);
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
/* Search for a available device starting at the last used device */
for (i = 0, j = last_used + 1; i < c; i++, j++)
{
if (j == c)
{
j = 0;
}
pvt = round_robin[j];
*exists = 1;
ast_mutex_lock (&pvt->lock);
if (can_dial(pvt, opts, requestor))
{
pvt->group_last_used = 1;
found = pvt;
break;
}
ast_mutex_unlock (&pvt->lock);
}
}
}
else if (((resource[0] == 'p') || (resource[0] == 'P')) && resource[1] == ':')
{
/* Generate a list of all available devices */
j = ITEMS_OF(round_robin);
c = 0; last_used = 0;
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (!strcmp (pvt->provider_name, &resource[2]))
{
round_robin[c] = pvt;
if (pvt->prov_last_used == 1)
{
pvt->prov_last_used = 0;
last_used = c;
}
c++;
if (c == j)
{
ast_mutex_unlock (&pvt->lock);
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
/* Search for a available device starting at the last used device */
for (i = 0, j = last_used + 1; i < c; i++, j++)
{
if (j == c)
{
j = 0;
}
pvt = round_robin[j];
*exists = 1;
ast_mutex_lock (&pvt->lock);
if (can_dial(pvt, opts, requestor))
{
pvt->prov_last_used = 1;
found = pvt;
break;
}
ast_mutex_unlock (&pvt->lock);
}
}
else if (((resource[0] == 's') || (resource[0] == 'S')) && resource[1] == ':')
{
/* Generate a list of all available devices */
j = ITEMS_OF(round_robin);
c = 0; last_used = 0;
i = strlen (&resource[2]);
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (!strncmp (pvt->imsi, &resource[2], i))
{
round_robin[c] = pvt;
if (pvt->sim_last_used == 1)
{
pvt->sim_last_used = 0;
last_used = c;
}
c++;
if (c == j)
{
ast_mutex_unlock (&pvt->lock);
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
/* Search for a available device starting at the last used device */
for (i = 0, j = last_used + 1; i < c; i++, j++)
{
if (j == c)
{
j = 0;
}
pvt = round_robin[j];
*exists = 1;
ast_mutex_lock (&pvt->lock);
if (can_dial(pvt, opts, requestor))
{
pvt->sim_last_used = 1;
found = pvt;
break;
}
ast_mutex_unlock (&pvt->lock);
}
}
else if (((resource[0] == 'i') || (resource[0] == 'I')) && resource[1] == ':')
{
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (!strcmp(pvt->imei, &resource[2]))
{
*exists = 1;
if(can_dial(pvt, opts, requestor))
{
found = pvt;
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
}
else
{
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock (&pvt->lock);
if (!strcmp (PVT_ID(pvt), resource))
{
*exists = 1;
if(can_dial(pvt, opts, requestor))
{
found = pvt;
break;
}
}
ast_mutex_unlock (&pvt->lock);
}
}
AST_RWLIST_UNLOCK(&state->devices);
return found;
}
#/* */
static const char * pvt_state_base(const struct pvt * pvt)
{
const char * state = NULL;
// length is "AAAAAAAAAA"
if(pvt->current_state == DEV_STATE_STOPPED && pvt->desired_state == DEV_STATE_STOPPED)
state = "Stopped";
else if(!pvt->connected)
state = "Not connected";
else if(!pvt->initialized)
state = "Not initialized";
else if(!pvt->gsm_registered)
state = "GSM not registered";
return state;
}
#/* */
EXPORT_DEF const char* pvt_str_state(const struct pvt* pvt)
{
const char * state = pvt_state_base(pvt);
if(!state) {
if(pvt->ring || PVT_STATE(pvt, chan_count[CALL_STATE_INCOMING]))
state = "Ring";
else if(pvt->cwaiting || PVT_STATE(pvt, chan_count[CALL_STATE_WAITING]))
state = "Waiting";
else if(pvt->dialing ||
(PVT_STATE(pvt, chan_count[CALL_STATE_INIT])
+
PVT_STATE(pvt, chan_count[CALL_STATE_DIALING])
+
PVT_STATE(pvt, chan_count[CALL_STATE_ALERTING])) > 0)
state = "Dialing";
else if(PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]) > 0)
// state = "Active";
state = pvt_call_dir(pvt);
else if(PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]) > 0)
state = "Held";
else if(pvt->outgoing_sms || pvt->incoming_sms_index != -1U)
state = "SMS";
else
state = "Free";
}
return state;
}
#/* */
EXPORT_DEF struct ast_str* pvt_str_state_ex(const struct pvt* pvt)
{
struct ast_str* buf = ast_str_create (256);
const char * state = pvt_state_base(pvt);
if(state)
ast_str_append (&buf, 0, "%s", state);
else
{
if(pvt->ring || PVT_STATE(pvt, chan_count[CALL_STATE_INCOMING]))
ast_str_append (&buf, 0, "Ring ");
if(pvt->dialing ||
(PVT_STATE(pvt, chan_count[CALL_STATE_INIT])
+
PVT_STATE(pvt, chan_count[CALL_STATE_DIALING])
+
PVT_STATE(pvt, chan_count[CALL_STATE_ALERTING])) > 0)
ast_str_append (&buf, 0, "Dialing ");
if(pvt->cwaiting || PVT_STATE(pvt, chan_count[CALL_STATE_WAITING]))
ast_str_append (&buf, 0, "Waiting ");
if(PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]) > 0)
ast_str_append (&buf, 0, "Active %u ", PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]));
if(PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]) > 0)
ast_str_append (&buf, 0, "Held %u ", PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]));
if(pvt->incoming_sms_index != -1U)
ast_str_append (&buf, 0, "Incoming SMS ");
if(pvt->outgoing_sms)
ast_str_append (&buf, 0, "Outgoing SMS");
if(ast_str_strlen(buf) == 0)
{
ast_str_append (&buf, 0, "%s", "Free");
}
}
if(pvt->desired_state != pvt->current_state)
ast_str_append (&buf, 0, " %s", dev_state2str_msg(pvt->desired_state));
return buf;
}
#/* */
EXPORT_DEF const char* GSM_regstate2str(int gsm_reg_status)
{
static const char * const gsm_states[] = {
"Not registered, not searching",
"Registered, home network",
"Not registered, but searching",
"Registration denied",
"Unknown",
"Registered, roaming",
};
return enum2str_def(gsm_reg_status, gsm_states, ITEMS_OF (gsm_states), "Unknown");
}
#/* */
EXPORT_DEF const char* sys_mode2str(int sys_mode)
{
static const char * const sys_modes[] = {
"No Service",
"AMPS",
"CDMA",
"GSM/GPRS",
"HDR",
"WCDMA",
"GPS",
};
return enum2str_def(sys_mode, sys_modes, ITEMS_OF (sys_modes), "Unknown");
}
#/* */
EXPORT_DEF const char * sys_submode2str(int sys_submode)
{
static const char * const sys_submodes[] = {
"No service",
"GSM",
"GPRS",
"EDGE",
"WCDMA",
"HSDPA",
"HSUPA",
"HSDPA and HSUPA",
};
return enum2str_def(sys_submode, sys_submodes, ITEMS_OF (sys_submodes), "Unknown");
}
#/* BUGFIX of https://code.google.com/p/asterisk-chan-dongle/issues/detail?id=118 */
EXPORT_DEF char* rssi2dBm(int rssi, char * buf, unsigned len)
{
#if 0 /* old code */
if(rssi <= 0)
{
snprintf(buf, len, "<= -125 dBm");
}
else if(rssi <= 30)
{
snprintf(buf, len, "%d dBm", 31 * rssi / 50 - 125);
}
else if(rssi == 31)
{
snprintf(buf, len, ">= -75 dBm");
}
else
{
snprintf(buf, len, "unknown");
}
#else
if(rssi <= 0)
{
snprintf(buf, len, "<= -113 dBm");
}
else if(rssi <= 30)
{
snprintf(buf, len, "%d dBm", 2 * rssi - 113);
}
else if(rssi == 31)
{
snprintf(buf, len, ">= -51 dBm");
}
else
{
snprintf(buf, len, "unknown or unmeasurable");
}
#endif
return buf;
}
/* Module */
#/* */
EXPORT_DEF void pvt_dsp_setup(struct pvt * pvt, const char * id, dc_dtmf_setting_t dtmf_new)
{
/* first remove dsp if off or changed */
if(dtmf_new != CONF_SHARED(pvt, dtmf))
{
if(pvt->dsp)
{
ast_dsp_free(pvt->dsp);
pvt->dsp = NULL;
}
}
/* wake up and initialize dsp */
if(dtmf_new != DC_DTMF_SETTING_OFF)
{
pvt->dsp = ast_dsp_new();
if(pvt->dsp)
{
int digitmode = DSP_DIGITMODE_DTMF;
if(dtmf_new == DC_DTMF_SETTING_RELAX)
digitmode |= DSP_DIGITMODE_RELAXDTMF;
ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT);
ast_dsp_set_digitmode(pvt->dsp, digitmode);
}
else
{
ast_log(LOG_ERROR, "[%s] Can't setup dsp for dtmf detection, ignored\n", id);
}
}
pvt->real_dtmf = dtmf_new;
}
static struct pvt * pvt_create(const pvt_config_t * settings)
{
struct pvt * pvt = ast_calloc (1, sizeof (*pvt));
if(pvt)
{
ast_mutex_init (&pvt->lock);
AST_LIST_HEAD_INIT_NOLOCK (&pvt->at_queue);
AST_LIST_HEAD_INIT_NOLOCK (&pvt->chans);
pvt->sys_chan.pvt = pvt;
pvt->sys_chan.state = CALL_STATE_RELEASED;
pvt->monitor_thread = AST_PTHREADT_NULL;
pvt->audio_fd = -1;
pvt->data_fd = -1;
pvt->timeout = DATA_READ_TIMEOUT;
pvt->gsm_reg_status = -1;
pvt->incoming_sms_index = -1U;
ast_copy_string (pvt->provider_name, "NONE", sizeof (pvt->provider_name));
ast_copy_string (pvt->subscriber_number, "Unknown", sizeof (pvt->subscriber_number));
pvt->has_subscriber_number = 0;
pvt->desired_state = SCONFIG(settings, initstate);
pvt_dsp_setup(pvt, UCONFIG(settings, id), SCONFIG(settings, dtmf));
/* and copy settings */
memcpy(&pvt->settings, settings, sizeof(pvt->settings));
return pvt;
}
else
{
ast_log (LOG_ERROR, "[%s] Skipping device: Error allocating memory\n", UCONFIG(settings, id));
}
return NULL;
}
#/* */
static int pvt_time4restate(const struct pvt * pvt)
{
if(pvt->desired_state != pvt->current_state)
{
if(pvt->restart_time == RESTATE_TIME_NOW || (PVT_NO_CHANS(pvt) && !pvt->outgoing_sms && pvt->incoming_sms_index == -1U))
return 1;
}
return 0;
}
#/* */
EXPORT_DEF void pvt_try_restate(struct pvt * pvt)
{
if(pvt_time4restate(pvt))
{
pvt->restart_time = RESTATE_TIME_NOW;
discovery_restart(gpublic);
}
}
#/* assume caller hold lock */
static int pvt_reconfigure(struct pvt * pvt, const pvt_config_t * settings, restate_time_t when)
{
int rv = 0;
if(SCONFIG(settings, initstate) == DEV_STATE_REMOVED)
{
/* handle later, in one place */
pvt->must_remove = 1;
}
else
{
/* check what changes require starting or stopping */
if(pvt->desired_state != SCONFIG(settings, initstate)) {
pvt->desired_state = SCONFIG(settings, initstate);
rv = pvt_time4restate(pvt);
pvt->restart_time = rv ? RESTATE_TIME_NOW : when;
}
/* check what config changes require restaring */
else if(strcmp(UCONFIG(settings, audio_tty), CONF_UNIQ(pvt, audio_tty))
|| strcmp(UCONFIG(settings, data_tty), CONF_UNIQ(pvt, data_tty))
|| strcmp(UCONFIG(settings, imei), CONF_UNIQ(pvt, imei))
|| strcmp(UCONFIG(settings, imsi), CONF_UNIQ(pvt, imsi))
|| SCONFIG(settings, u2diag) != CONF_SHARED(pvt, u2diag)
|| SCONFIG(settings, resetdongle) != CONF_SHARED(pvt, resetdongle)
|| SCONFIG(settings, callwaiting) != CONF_SHARED(pvt, callwaiting))
{
/* TODO: schedule restart */
pvt->desired_state = DEV_STATE_RESTARTED;
rv = pvt_time4restate(pvt);
pvt->restart_time = rv ? RESTATE_TIME_NOW : when;
}
pvt_dsp_setup(pvt, UCONFIG(settings, id), SCONFIG(settings, dtmf));
/* and copy settings */
memcpy(&pvt->settings, settings, sizeof(pvt->settings));
}
return rv;
}
#/* */
static int reload_config(public_state_t * state, int recofigure, restate_time_t when, unsigned * reload_immediality)
{
struct ast_config * cfg;
const char * cat;
struct ast_flags config_flags = { 0 };
struct dc_sconfig config_defaults;
pvt_config_t settings;
int err;
struct pvt * pvt;
unsigned reload_now = 0;
if ((cfg = ast_config_load (CONFIG_FILE, config_flags)) == NULL)
{
return -1;
}
/* read global config */
dc_gconfig_fill(cfg, "general", &state->global_settings);
/* read defaults */
dc_sconfig_fill_defaults(&config_defaults);
dc_sconfig_fill(cfg, "defaults", &config_defaults);
/* FIXME: deadlock avoid ? */
AST_RWLIST_RDLOCK(&state->devices);
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock(&pvt->lock);
pvt->must_remove = 1;
ast_mutex_unlock(&pvt->lock);
}
AST_RWLIST_UNLOCK(&state->devices);
/* now load devices */
for (cat = ast_category_browse (cfg, NULL); cat; cat = ast_category_browse (cfg, cat))
{
if (strcasecmp (cat, "general") && strcasecmp (cat, "defaults"))
{
err = dc_config_fill(cfg, cat, &config_defaults, &settings);
if(!err)
{
pvt = find_device(UCONFIG(&settings, id));
if(pvt)
{
if(!recofigure)
{
ast_log (LOG_ERROR, "device %s already exists, duplicate in config file\n", cat);
}
else
{
pvt->must_remove = 0;
reload_now += pvt_reconfigure(pvt, &settings, when);
}
ast_mutex_unlock(&pvt->lock);
}
else
{
/* new device */
if(SCONFIG(&settings, initstate) == DEV_STATE_REMOVED)
{
ast_log (LOG_NOTICE, "Skipping device %s as disabled\n", cat);
}
else
{
pvt = pvt_create(&settings);
if(pvt)
{
/* FIXME: deadlock avoid ? */
AST_RWLIST_WRLOCK(&state->devices);
AST_RWLIST_INSERT_TAIL(&state->devices, pvt, entry);
AST_RWLIST_UNLOCK(&state->devices);
reload_now++;
ast_log (LOG_NOTICE, "[%s] Loaded device\n", PVT_ID(pvt));
}
}
}
}
}
}
ast_config_destroy (cfg);
/* FIXME: deadlock avoid ? */
/* schedule removal of devices not listed in config file or disabled */
AST_RWLIST_RDLOCK(&state->devices);
AST_RWLIST_TRAVERSE(&state->devices, pvt, entry)
{
ast_mutex_lock(&pvt->lock);
if(pvt->must_remove)
{
pvt->desired_state = DEV_STATE_REMOVED;
if(pvt_time4restate(pvt))
{
pvt->restart_time = RESTATE_TIME_NOW;
reload_now++;
}
else
pvt->restart_time = when;
}
ast_mutex_unlock(&pvt->lock);
}
AST_RWLIST_UNLOCK (&state->devices);
if(reload_immediality)
*reload_immediality = reload_now;
return 0;
}
#/* */
static void devices_destroy(public_state_t * state)
{
struct pvt * pvt;
/* Destroy the device list */
AST_RWLIST_WRLOCK(&state->devices);
while((pvt = AST_RWLIST_REMOVE_HEAD(&state->devices, entry)))
{
pvt_destroy(pvt);
}
AST_RWLIST_UNLOCK(&state->devices);
}
static int load_module()
{
int rv;
gpublic = ast_calloc(1, sizeof(*gpublic));
if(gpublic)
{
pdiscovery_init();
rv = public_state_init(gpublic);
if(rv != AST_MODULE_LOAD_SUCCESS)
ast_free(gpublic);
}
else
{
ast_log (LOG_ERROR, "Unable to allocate global state structure\n");
rv = AST_MODULE_LOAD_DECLINE;
}
return rv;
}
#/* */
static int public_state_init(struct public_state * state)
{
int rv = AST_MODULE_LOAD_DECLINE;
AST_RWLIST_HEAD_INIT(&state->devices);
ast_mutex_init(&state->discovery_lock);
state->discovery_thread = AST_PTHREADT_NULL;
if(reload_config(state, 0, RESTATE_TIME_NOW, NULL) == 0)
{
rv = AST_MODULE_LOAD_FAILURE;
if(discovery_restart(state) == 0)
{
/* set preferred capabilities */
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
if (!(channel_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
return AST_MODULE_LOAD_FAILURE;
}
ast_format_cap_append(channel_tech.capabilities, ast_format_slin, 0);
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
ast_format_set(&chan_dongle_format, AST_FORMAT_SLINEAR, 0);
# if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
if (!(channel_tech.capabilities = ast_format_cap_alloc(0))) {
return AST_MODULE_LOAD_FAILURE;
}
# else
if (!(channel_tech.capabilities = ast_format_cap_alloc())) {
return AST_MODULE_LOAD_FAILURE;
}
# endif
ast_format_cap_add(channel_tech.capabilities, &chan_dongle_format);
chan_dongle_format_cap = channel_tech.capabilities;
#endif /* ^10-13 */
/* register our channel type */
if(ast_channel_register(&channel_tech) == 0)
{
if (smsdb_init() != 0) {
ast_log(LOG_ERROR, "Unable to init smsdb\n");
}
cli_register();
app_register();
manager_register();
return AST_MODULE_LOAD_SUCCESS;
}
else
{
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
ao2_cleanup(channel_tech.capabilities);
channel_tech.capabilities = NULL;
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
channel_tech.capabilities = ast_format_cap_destroy(channel_tech.capabilities);
#endif /* ^10-13 */
ast_log (LOG_ERROR, "Unable to register channel class %s\n", channel_tech.type);
}
discovery_stop(state);
}
else
{
ast_log (LOG_ERROR, "Unable to create discovery thread\n");
}
devices_destroy(state);
}
else
{
ast_log (LOG_ERROR, "Errors reading config file " CONFIG_FILE ", Not loading module\n");
}
ast_mutex_destroy(&state->discovery_lock);
AST_RWLIST_HEAD_DESTROY(&state->devices);
return rv;
}
#/* */
static void public_state_fini(struct public_state * state)
{
/* First, take us out of the channel loop */
ast_channel_unregister (&channel_tech);
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
ao2_cleanup(channel_tech.capabilities);
channel_tech.capabilities = NULL;
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
channel_tech.capabilities = ast_format_cap_destroy(channel_tech.capabilities);
#endif /* ^10-13 */
/* Unregister the CLI & APP & MANAGER */
manager_unregister();
app_unregister();
cli_unregister();
discovery_stop(state);
devices_destroy(state);
ast_mutex_destroy(&state->discovery_lock);
AST_RWLIST_HEAD_DESTROY(&state->devices);
}
static int unload_module()
{
public_state_fini(gpublic);
pdiscovery_fini();
ast_free(gpublic);
smsdb_atexit();
gpublic = NULL;
return 0;
}
#/* */
EXPORT_DEF void pvt_reload(restate_time_t when)
{
unsigned dev_reload = 0;
reload_config(gpublic, 1, when, &dev_reload);
if(dev_reload > 0)
discovery_restart(gpublic);
}
#/* */
static int reload_module()
{
pvt_reload(RESTATE_TIME_GRACEFULLY);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, MODULE_DESCRIPTION,
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
.support_level = AST_MODULE_SUPPORT_EXTENDED,
#endif /* ^13+ */
.load = load_module,
.unload = unload_module,
.reload = reload_module,
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
.load_pri = AST_MODPRI_CHANNEL_DRIVER,
#endif /* ^13+ */
);
//AST_MODULE_INFO_STANDARD (ASTERISK_GPL_KEY, MODULE_DESCRIPTION);
EXPORT_DEF struct ast_module* self_module(void)
{
return ast_module_info->self;
}