summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzuul <zuul@gerrit.asterisk.org>2016-03-03 16:42:04 -0600
committerGerrit Code Review <gerrit2@gerrit.digium.api>2016-03-03 16:42:04 -0600
commitd2be16472efa0608c389b467ad69044fbe8e9f5f (patch)
treea98b94acf8ad42739315ed96a6e03d868b971054
parent039ea76a8a47a4b1464dcdb3b46161d5320bf4e9 (diff)
parent40d9e9e2384fcf1da715d7a34680f02631c8ec64 (diff)
Merge "bridge.c: Crash during attended transfer when missing a local channel half" into 13
-rw-r--r--include/asterisk/core_local.h32
-rw-r--r--main/bridge.c22
-rw-r--r--main/core_local.c39
3 files changed, 85 insertions, 8 deletions
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 491112d2b..8557072c6 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -42,6 +42,38 @@ struct stasis_message_type;
/* ------------------------------------------------------------------- */
/*!
+ * \brief Lock the "chan" and "owner" channels (and return them) on the base
+ * private structure as well as the base private structure itself.
+ *
+ * \note This also adds references to each of the above mentioned elements and
+ * also the underlying private local structure.
+ * \note None of these locks should be held prior to calling this function.
+ * \note To undo this process call ast_local_unlock_all.
+ *
+ * \since 13.8.0
+ *
+ * \param chan Must be a local channel
+ * \param outchan The local channel's "chan" channel
+ * \param outowner The local channel's "owner" channel
+ */
+void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
+ struct ast_channel **outowner);
+
+/*!
+ * \brief Unlock the "chan" and "owner" channels on the base private structure
+ * as well as the base private structure itself.
+ *
+ * \note This also removes references to each of the above mentioned elements and
+ * also the underlying private local structure.
+ * \note This function should be used in conjunction with ast_local_lock_all.
+ *
+ * \since 13.8.0
+ *
+ * \param chan Must be a local channel
+ */
+void ast_local_unlock_all(struct ast_channel *chan);
+
+/*!
* \brief Get the other local channel in the pair.
* \since 12.0.0
*
diff --git a/main/bridge.c b/main/bridge.c
index 9d8d807f8..fd83cfb7b 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -4036,19 +4036,25 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
if (bridge2) {
- RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup);
struct ast_channel *locals[2];
- ast_channel_lock(local_chan);
- local_chan2 = ast_local_get_peer(local_chan);
- ast_channel_unlock(local_chan);
-
- ast_assert(local_chan2 != NULL);
+ /* Have to lock everything just in case a hangup comes in early */
+ ast_local_lock_all(local_chan, &locals[0], &locals[1]);
+ if (!locals[0] || !locals[1]) {
+ ast_log(LOG_ERROR, "Transfer failed probably due to an early hangup - "
+ "missing other half of '%s'\n", ast_channel_name(local_chan));
+ ast_local_unlock_all(local_chan);
+ ao2_cleanup(local_chan);
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
- locals[0] = local_chan;
- locals[1] = local_chan2;
+ /* Make sure the peer is properly set */
+ if (local_chan != locals[0]) {
+ SWAP(locals[0], locals[1]);
+ }
ast_attended_transfer_message_add_link(transfer_msg, locals);
+ ast_local_unlock_all(local_chan);
} else {
ast_attended_transfer_message_add_app(transfer_msg, app, local_chan);
}
diff --git a/main/core_local.c b/main/core_local.c
index 10bd83961..1b8ebf6f1 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -235,6 +235,45 @@ struct local_pvt {
char exten[AST_MAX_EXTENSION];
};
+void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
+ struct ast_channel **outowner)
+{
+ struct local_pvt *p = ast_channel_tech_pvt(chan);
+
+ *outchan = NULL;
+ *outowner = NULL;
+
+ if (p) {
+ ao2_ref(p, 1);
+ ast_unreal_lock_all(&p->base, outchan, outowner);
+ }
+}
+
+void ast_local_unlock_all(struct ast_channel *chan)
+{
+ struct local_pvt *p = ast_channel_tech_pvt(chan);
+ struct ast_unreal_pvt *base;
+
+ if (!p) {
+ return;
+ }
+
+ base = &p->base;
+
+ if (base->owner) {
+ ast_channel_unlock(base->owner);
+ ast_channel_unref(base->owner);
+ }
+
+ if (base->chan) {
+ ast_channel_unlock(base->chan);
+ ast_channel_unref(base->chan);
+ }
+
+ ao2_unlock(base);
+ ao2_ref(p, -1);
+}
+
struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
{
struct local_pvt *p = ast_channel_tech_pvt(ast);