FT_DECLARE(char *) ftdm_channel_get_history_str(const ftdm_channel_t *fchan) { uint8_t i = 0; ftdm_time_t currtime = 0; ftdm_time_t prevtime = 0; ftdm_stream_handle_t stream = { 0 }; FTDM_STANDARD_STREAM(stream); if (!fchan->history[0].file) { stream.write_function(&stream, "-- No state history --\n"); return stream.data; } stream.write_function(&stream, "%-30.30s %-30.30s %-30.30s %s", "-- States --", "-- Function --", "-- Location --", "-- Time Offset --\n"); for (i = fchan->hindex; i < ftdm_array_len(fchan->history); i++) { if (!fchan->history[i].file) { break; } write_history_entry(fchan, &stream, i, &prevtime); } for (i = 0; i < fchan->hindex; i++) { write_history_entry(fchan, &stream, i, &prevtime); } currtime = ftdm_current_time_in_ms(); stream.write_function(&stream, "\nTime since last state change: %lums\n", (currtime - prevtime)); return stream.data; }
static void tap_pri_put_pcall(pritap_t *pritap, void *callref) { int i; int crv; int tstcrv; if (!callref) { ftdm_log(FTDM_LOG_ERROR, "Cannot put pcall for null callref in span %s\n", pritap->span->name); return; } ftdm_mutex_lock(pritap->pcalls_lock); crv = tap_pri_get_crv(pritap->pri, callref); for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) { if (!pritap->pcalls[i].callref) { continue; } tstcrv = tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref); if (tstcrv == crv) { if (pritap->pcalls[i].inuse) { ftdm_log(FTDM_LOG_DEBUG, "releasing slot %d in span %s used by callref %d/%p\n", i, pritap->span->name, crv, pritap->pcalls[i].callref); pritap->pcalls[i].inuse = 0; } } } ftdm_mutex_unlock(pritap->pcalls_lock); }
static passive_call_t *tap_pri_get_pcall_bycrv(pritap_t *pritap, int crv) { int i; int tstcrv; ftdm_mutex_lock(pritap->pcalls_lock); for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) { tstcrv = pritap->pcalls[i].callref ? tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref) : 0; if (pritap->pcalls[i].callref && tstcrv == crv) { if (pritap->pcalls[i].inuse) { ftdm_mutex_unlock(pritap->pcalls_lock); return &pritap->pcalls[i]; } /* This just means the crv is being re-used in another call before this one was destroyed */ ftdm_log(FTDM_LOG_DEBUG, "Found crv %d in slot %d of span %s with call %p but is no longer in use\n", crv, i, pritap->span->name, pritap->pcalls[i].callref); } } ftdm_log(FTDM_LOG_DEBUG, "crv %d was not found active in span %s\n", crv, pritap->span->name); ftdm_mutex_unlock(pritap->pcalls_lock); return NULL; }
ftdm_status_t sngisdn_transfer(ftdm_channel_t *ftdmchan) { const char* args; char *p; char *type = NULL; char *target = NULL; ftdm_status_t status = FTDM_FAIL; sngisdn_chan_data_t *sngisdn_info = ftdmchan->call_data; unsigned i; args = ftdm_usrmsg_get_var(ftdmchan->usrmsg, "transfer_arg"); if (ftdm_strlen_zero(args)) { ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Cannot perform transfer because call_transfer_arg variable is not set\n"); goto done; } type = ftdm_strdup(args); if ((p = strchr(type, '/'))) { target = ftdm_strdup(p+1); *p = '\0'; } if (ftdm_strlen_zero(type) || ftdm_strlen_zero(target)) { ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Invalid parameters for transfer %s, expected <type>/<target>\n", args); goto done; } if (sngisdn_info->transfer_data.type != SNGISDN_TRANSFER_NONE) { ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Cannot perform transfer because an existing transfer transfer is pending (%s)\n", sngisdn_transfer_type2str(sngisdn_info->transfer_data.type)); goto done; } ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Transfer requested type:%s target:%s\n", type, target); for (i = 0; i < ftdm_array_len(transfer_interfaces); i++ ) { if (!strcasecmp(transfer_interfaces[i].name, type)) { /* Depending on the transfer type, the transfer function may change the * channel state to UP, or last_state, but the transfer function will always result in * an immediate state change if FTDM_SUCCESS is returned */ status = transfer_interfaces[i].func(ftdmchan, transfer_interfaces[i].type, target); goto done; } } ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Invalid transfer type:%s\n", type); done: if (status != FTDM_SUCCESS) { ftdm_set_state(ftdmchan, ftdmchan->last_state); } ftdm_safe_free(type); ftdm_safe_free(target); return status; }
FT_DECLARE(ftdm_status_t) ftdm_channel_cancel_state(const char *file, const char *func, int line, ftdm_channel_t *fchan) { ftdm_time_t diff; ftdm_channel_state_t state; ftdm_channel_state_t last_state; uint8_t hindex = 0; if (!ftdm_test_flag(fchan, FTDM_CHANNEL_STATE_CHANGE)) { ftdm_log_chan(fchan, FTDM_LOG_WARNING, "Cannot cancel state change from %s to %s, it was already processed\n", ftdm_channel_state2str(fchan->last_state), ftdm_channel_state2str(fchan->state)); return FTDM_FAIL; } if (fchan->state_status != FTDM_STATE_STATUS_NEW) { ftdm_log_chan(fchan, FTDM_LOG_WARNING, "Failed to cancel state change from %s to %s, state is not new anymore\n", ftdm_channel_state2str(fchan->last_state), ftdm_channel_state2str(fchan->state)); return FTDM_FAIL; } /* compute the last history index */ hindex = (fchan->hindex == 0) ? (ftdm_array_len(fchan->history) - 1) : (fchan->hindex - 1); diff = fchan->history[hindex].end_time - fchan->history[hindex].time; /* go back in time and revert the state to the previous state */ state = fchan->state; last_state = fchan->last_state; fchan->state = fchan->last_state; fchan->state_status = FTDM_STATE_STATUS_COMPLETED; fchan->last_state = fchan->history[hindex].last_state; fchan->hindex = hindex; /* clear the state change flag */ ftdm_clear_flag(fchan, FTDM_CHANNEL_STATE_CHANGE); /* ack any pending indications as cancelled */ ftdm_ack_indication(fchan, fchan->indication, FTDM_ECANCELED); /* wake up anyone sleeping waiting for the state change to complete, it won't ever be completed */ if (ftdm_test_flag(fchan, FTDM_CHANNEL_BLOCKING)) { ftdm_clear_flag(fchan, FTDM_CHANNEL_BLOCKING); ftdm_interrupt_signal(fchan->state_completed_interrupt); } /* NOTE * we could potentially also take out the channel from the pendingchans queue, but I believe is easier just leave it, * the only side effect will be a call to ftdm_channel_advance_states() for a channel that has nothing to advance */ ftdm_log_chan_ex(fchan, file, func, line, FTDM_LOG_LEVEL_DEBUG, "Cancelled state change from %s to %s in %llums\n", ftdm_channel_state2str(last_state), ftdm_channel_state2str(state), diff); return FTDM_SUCCESS; }
/* * This is a tricky function with some side effects, some explanation needed ... * * The libpri stack process HDLC frames, then finds Q921 frames and Q931 events, each time * it finds a new Q931 event, checks if the crv of that event matches a known call in the internal * list found in the PRI control block (for us, one control block per span), if it does not find * the call, allocates a new one and then sends the event up to the user (us, ftmod_pritap in this case) * * The user is then expected to destroy the call when done with it (on hangup), but things get tricky here * because in ftmod_pritap we do not destroy the call right away to be sure we only destroy it when no one * else needs that pointer, therefore we decide to delay the destruction of the call pointer until later * when a new call comes which triggers the garbage collecting code in this function * * Now, what happens if a new call arrives right away with the same crv than the last call? the pri stack * does *not* allocate a new call pointer because is still a known call and we must therefore re-use the * same call pointer * * This function accepts a pointer to a callref, even a NULL one. When callref is NULL we search for an * available slot so the caller of this function can use it to store a new callref pointer. In the process * we also scan for slots that still have a callref pointer but are no longer in use (inuse=0) and we * destroy that callref and clear the slot (memset). The trick is, we only do this if the callref to * be garbage collected is NOT the one provided by the parameter callref, of course! otherwise we may * be freeing a pointer to a callref for a new call that used an old (recycled) callref! */ static passive_call_t *tap_pri_get_pcall(pritap_t *pritap, void *callref) { int i; int crv; ftdm_mutex_lock(pritap->pcalls_lock); for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) { /* If this slot has a call reference * and it is different than the *callref provided to us * and is no longer in use, * then it is time to garbage collect it ... */ if (pritap->pcalls[i].callref && callref != pritap->pcalls[i].callref && !pritap->pcalls[i].inuse) { crv = tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref); /* garbage collection */ ftdm_log(FTDM_LOG_DEBUG, "Garbage collecting callref %d/%p from span %s in slot %d\n", crv, pritap->pcalls[i].callref, pritap->span->name, i); pri_passive_destroycall(pritap->pri, pritap->pcalls[i].callref); memset(&pritap->pcalls[i], 0, sizeof(pritap->pcalls[0])); } if (callref == pritap->pcalls[i].callref) { if (callref == NULL) { pritap->pcalls[i].inuse = 1; ftdm_log(FTDM_LOG_DEBUG, "Enabling callref slot %d in span %s\n", i, pritap->span->name); } else if (!pritap->pcalls[i].inuse) { crv = tap_pri_get_crv(pritap->pri, callref); ftdm_log(FTDM_LOG_DEBUG, "Recyclying callref slot %d in span %s for callref %d/%p\n", i, pritap->span->name, crv, callref); memset(&pritap->pcalls[i], 0, sizeof(pritap->pcalls[0])); pritap->pcalls[i].callref = callref; pritap->pcalls[i].inuse = 1; } ftdm_mutex_unlock(pritap->pcalls_lock); return &pritap->pcalls[i]; } } ftdm_mutex_unlock(pritap->pcalls_lock); return NULL; }
FT_DECLARE(ftdm_status_t) ftdm_backtrace_walk(void (* callback)(const int tid, const void *addr, const char *symbol, void *priv), void *priv) { void *stacktrace[FTDM_BACKTRACE_MAX]; char **symbols = NULL; size_t size = 0; pid_t tid = 0; int si = 0; if (!callback) { return FTDM_EINVAL; } tid = syscall(SYS_gettid); size = backtrace(stacktrace, ftdm_array_len(stacktrace)); symbols = backtrace_symbols(stacktrace, size); for (si = 0; si < size; si++) { callback(tid, stacktrace[si], symbols[si], priv); } free(symbols); return FTDM_SUCCESS; }
FT_DECLARE(ftdm_status_t) _ftdm_channel_complete_state(const char *file, const char *func, int line, ftdm_channel_t *fchan) { uint8_t hindex = 0; ftdm_time_t diff = 0; ftdm_channel_state_t state = fchan->state; if (fchan->state_status == FTDM_STATE_STATUS_COMPLETED) { ftdm_assert_return(!ftdm_test_flag(fchan, FTDM_CHANNEL_STATE_CHANGE), FTDM_FAIL, "State change flag set but state is not completed\n"); return FTDM_SUCCESS; } ftdm_usrmsg_free(&fchan->usrmsg); ftdm_clear_flag(fchan, FTDM_CHANNEL_STATE_CHANGE); if (state == FTDM_CHANNEL_STATE_PROGRESS) { ftdm_set_flag(fchan, FTDM_CHANNEL_PROGRESS); } else if (state == FTDM_CHANNEL_STATE_PROGRESS_MEDIA) { ftdm_set_flag(fchan, FTDM_CHANNEL_PROGRESS); ftdm_test_and_set_media(fchan); } else if (state == FTDM_CHANNEL_STATE_UP) { ftdm_set_flag(fchan, FTDM_CHANNEL_PROGRESS); ftdm_set_flag(fchan, FTDM_CHANNEL_ANSWERED); ftdm_test_and_set_media(fchan); } else if (state == FTDM_CHANNEL_STATE_DIALING) { ftdm_sigmsg_t msg; memset(&msg, 0, sizeof(msg)); msg.channel = fchan; msg.event_id = FTDM_SIGEVENT_DIALING; ftdm_span_send_signal(fchan->span, &msg); } else if (state == FTDM_CHANNEL_STATE_HANGUP) { ftdm_set_echocancel_call_end(fchan); } /* MAINTENANCE WARNING * we're assuming an indication performed * via state change will involve a single state change */ ftdm_ack_indication(fchan, fchan->indication, FTDM_SUCCESS); hindex = (fchan->hindex == 0) ? (ftdm_array_len(fchan->history) - 1) : (fchan->hindex - 1); ftdm_assert(!fchan->history[hindex].end_time, "End time should be zero!\n"); fchan->history[hindex].end_time = ftdm_current_time_in_ms(); fchan->state_status = FTDM_STATE_STATUS_COMPLETED; diff = fchan->history[hindex].end_time - fchan->history[hindex].time; ftdm_log_chan_ex(fchan, file, func, line, FTDM_LOG_LEVEL_DEBUG, "Completed state change from %s to %s in %llums\n", ftdm_channel_state2str(fchan->last_state), ftdm_channel_state2str(state), diff); if (ftdm_test_flag(fchan, FTDM_CHANNEL_BLOCKING)) { ftdm_clear_flag(fchan, FTDM_CHANNEL_BLOCKING); ftdm_interrupt_signal(fchan->state_completed_interrupt); } return FTDM_SUCCESS; }
static ftdm_status_t ftdm_core_set_state(const char *file, const char *func, int line, ftdm_channel_t *ftdmchan, ftdm_channel_state_t state, int waitrq) { ftdm_status_t status; int ok = 1; int waitms = DEFAULT_WAIT_TIME; if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_READY)) { ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_ERROR, "Ignored state change request from %s to %s, the channel is not ready\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state)); return FTDM_FAIL; } if (ftdmchan->state_status != FTDM_STATE_STATUS_COMPLETED) { ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_ERROR, "Ignored state change request from %s to %s, the previous state change has not been processed yet (status = %s)\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state), ftdm_state_status2str(ftdmchan->state_status)); return FTDM_FAIL; } if (ftdmchan->state == state) { ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_WARNING, "Why bother changing state from %s to %s\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state)); return FTDM_FAIL; } if (!ftdmchan->state_completed_interrupt) { status = ftdm_interrupt_create(&ftdmchan->state_completed_interrupt, FTDM_INVALID_SOCKET); if (status != FTDM_SUCCESS) { ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_CRIT, "Failed to create state change interrupt when moving from %s to %s\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state)); return status; } } if (ftdmchan->span->state_map) { ok = ftdm_parse_state_map(ftdmchan, state, ftdmchan->span->state_map); goto end; } /* basic core state validation (by-passed if the signaling module provides a state_map) */ switch(ftdmchan->state) { case FTDM_CHANNEL_STATE_HANGUP: case FTDM_CHANNEL_STATE_TERMINATING: { ok = 0; switch(state) { case FTDM_CHANNEL_STATE_DOWN: case FTDM_CHANNEL_STATE_BUSY: case FTDM_CHANNEL_STATE_RESTART: ok = 1; break; default: break; } } break; case FTDM_CHANNEL_STATE_UP: { ok = 1; switch(state) { case FTDM_CHANNEL_STATE_PROGRESS: case FTDM_CHANNEL_STATE_PROGRESS_MEDIA: case FTDM_CHANNEL_STATE_RING: ok = 0; break; default: break; } } break; case FTDM_CHANNEL_STATE_DOWN: { ok = 0; switch(state) { case FTDM_CHANNEL_STATE_DIALTONE: case FTDM_CHANNEL_STATE_COLLECT: case FTDM_CHANNEL_STATE_DIALING: case FTDM_CHANNEL_STATE_RING: case FTDM_CHANNEL_STATE_PROGRESS_MEDIA: case FTDM_CHANNEL_STATE_PROGRESS: case FTDM_CHANNEL_STATE_IDLE: case FTDM_CHANNEL_STATE_GET_CALLERID: case FTDM_CHANNEL_STATE_GENRING: ok = 1; break; default: break; } } break; case FTDM_CHANNEL_STATE_BUSY: { switch(state) { case FTDM_CHANNEL_STATE_UP: ok = 0; break; default: break; } } break; case FTDM_CHANNEL_STATE_RING: { switch(state) { case FTDM_CHANNEL_STATE_UP: ok = 1; break; default: break; } } break; default: break; } end: if (!ok) { ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_WARNING, "VETO state change from %s to %s\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state)); goto done; } ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_DEBUG, "Changed state from %s to %s\n", ftdm_channel_state2str(ftdmchan->state), ftdm_channel_state2str(state)); ftdmchan->last_state = ftdmchan->state; ftdmchan->state = state; ftdmchan->state_status = FTDM_STATE_STATUS_NEW; ftdmchan->history[ftdmchan->hindex].file = file; ftdmchan->history[ftdmchan->hindex].func = func; ftdmchan->history[ftdmchan->hindex].line = line; ftdmchan->history[ftdmchan->hindex].state = ftdmchan->state; ftdmchan->history[ftdmchan->hindex].last_state = ftdmchan->last_state; ftdmchan->history[ftdmchan->hindex].time = ftdm_current_time_in_ms(); ftdmchan->history[ftdmchan->hindex].end_time = 0; ftdmchan->hindex++; if (ftdmchan->hindex == ftdm_array_len(ftdmchan->history)) { ftdmchan->hindex = 0; } ftdm_set_flag(ftdmchan, FTDM_CHANNEL_STATE_CHANGE); ftdm_mutex_lock(ftdmchan->span->mutex); ftdm_set_flag(ftdmchan->span, FTDM_SPAN_STATE_CHANGE); if (ftdmchan->span->pendingchans) { ftdm_queue_enqueue(ftdmchan->span->pendingchans, ftdmchan); } ftdm_mutex_unlock(ftdmchan->span->mutex); if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_NONBLOCK)) { /* the channel should not block waiting for state processing */ goto done; } if (!waitrq) { /* no waiting was requested */ goto done; } /* let's wait for the state change to be completed by the signaling stack */ ftdm_set_flag(ftdmchan, FTDM_CHANNEL_BLOCKING); ftdm_mutex_unlock(ftdmchan->mutex); status = ftdm_interrupt_wait(ftdmchan->state_completed_interrupt, waitms); ftdm_mutex_lock(ftdmchan->mutex); if (status != FTDM_SUCCESS) { ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_BLOCKING); ftdm_log_chan_ex(ftdmchan, file, func, line, FTDM_LOG_LEVEL_WARNING, "state change from %s to %s was most likely not completed after aprox %dms\n", ftdm_channel_state2str(ftdmchan->last_state), ftdm_channel_state2str(state), DEFAULT_WAIT_TIME); ok = 0; goto done; } done: return ok ? FTDM_SUCCESS : FTDM_FAIL; }
FT_DECLARE(ftdm_status_t) ftdm_interrupt_multiple_wait(ftdm_interrupt_t *interrupts[], ftdm_size_t size, int ms) { int numdevices = 0; unsigned i; #if defined(__WINDOWS__) DWORD res = 0; HANDLE ints[20]; if (size > (ftdm_array_len(ints)/2)) { /* improve if needed: dynamically allocate the list of interrupts *only* when exceeding the default size */ ftdm_log(FTDM_LOG_CRIT, "Unsupported size of interrupts: %d, implement me!\n", size); return FTDM_FAIL; } for (i = 0; i < size; i++) { ints[i] = interrupts[i]->event; if (interrupts[i]->device != FTDM_INVALID_SOCKET) { ints[size+numdevices] = interrupts[i]->device; numdevices++; } } res = WaitForMultipleObjects((DWORD)size+numdevices, ints, FALSE, ms >= 0 ? ms : INFINITE); switch (res) { case WAIT_TIMEOUT: return FTDM_TIMEOUT; case WAIT_FAILED: case WAIT_ABANDONED: /* is it right to fail with abandoned? */ return FTDM_FAIL; default: if (res >= (size+numdevices)) { ftdm_log(FTDM_LOG_ERROR, "Error waiting for freetdm interrupt event (WaitForSingleObject returned %d)\n", res); return FTDM_FAIL; } /* fall-through to FTDM_SUCCESS at the end of the function */ } #elif defined(__linux__) int res = 0; char pipebuf[255]; struct pollfd ints[size*2]; memset(&ints, 0, sizeof(ints)); for (i = 0; i < size; i++) { ints[i].events = POLLIN; ints[i].revents = 0; ints[i].fd = interrupts[i]->readfd; if (interrupts[i]->device != FTDM_INVALID_SOCKET) { ints[size+numdevices].events = POLLIN; ints[size+numdevices].revents = 0; ints[size+numdevices].fd = interrupts[i]->device; numdevices++; } } res = poll(ints, size + numdevices, ms); if (res == -1) { ftdm_log(FTDM_LOG_CRIT, "interrupt poll failed (%s)\n", strerror(errno)); return FTDM_FAIL; } if (res == 0) { return FTDM_TIMEOUT; } /* check for events in the pipes, NOT in the devices */ for (i = 0; i < size; i++) { if (ints[i].revents & POLLIN) { res = read(ints[i].fd, pipebuf, sizeof(pipebuf)); if (res == -1) { ftdm_log(FTDM_LOG_CRIT, "reading interrupt descriptor failed (%s)\n", strerror(errno)); } } } #else #endif return FTDM_SUCCESS; }