diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index e42307dd92c86322af7c929204d8c8744cc33c46..a76e606f9e4e6c7587b3b0f83d73968a1e063ecc 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -4669,4 +4669,16 @@ int ast_channel_feature_hooks_append(struct ast_channel *chan, struct ast_bridge */ int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridge_features *features); +enum ast_channel_error { + /* Unable to determine what error occurred. */ + AST_CHANNEL_ERROR_UNKNOWN, + /* Channel with this ID already exists */ + AST_CHANNEL_ERROR_ID_EXISTS, +}; + +/*! + * \brief Get error code for latest channel operation. + */ +enum ast_channel_error ast_channel_errno(void); + #endif /* _ASTERISK_CHANNEL_H */ diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h index d1231b400a699e5eb1dcb609c8ad2dcb3c71c05c..2316e2f24c9e128860ecf15071e07b895abafaee 100644 --- a/include/asterisk/channel_internal.h +++ b/include/asterisk/channel_internal.h @@ -25,3 +25,5 @@ int ast_channel_internal_is_finalized(struct ast_channel *chan); void ast_channel_internal_cleanup(struct ast_channel *chan); int ast_channel_internal_setup_topics(struct ast_channel *chan); +void ast_channel_internal_errno_set(enum ast_channel_error error); +enum ast_channel_error ast_channel_internal_errno(void); diff --git a/main/channel.c b/main/channel.c index c6cb925b8c20c4637c95614db19b6924c09d5bcb..278104cc98cf1251b1c6fdc268ddbeacc9f74e4f 100644 --- a/main/channel.c +++ b/main/channel.c @@ -767,6 +767,27 @@ static const struct ast_channel_tech null_tech = { static void ast_channel_destructor(void *obj); static void ast_dummy_channel_destructor(void *obj); +static int ast_channel_by_uniqueid_cb(void *obj, void *arg, void *data, int flags); + +static int does_id_conflict(const char *uniqueid) +{ + struct ast_channel *conflict; + int length = 0; + + if (ast_strlen_zero(uniqueid)) { + return 0; + } + + conflict = ast_channel_callback(ast_channel_by_uniqueid_cb, (char *) uniqueid, &length, OBJ_NOLOCK); + if (conflict) { + ast_log(LOG_ERROR, "Channel Unique ID '%s' already in use by channel %s(%p)\n", + uniqueid, ast_channel_name(conflict), conflict); + ast_channel_unref(conflict); + return 1; + } + + return 0; +} /*! \brief Create a new channel structure */ static struct ast_channel * attribute_malloc __attribute__((format(printf, 15, 0))) @@ -940,16 +961,33 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char ast_channel_tech_set(tmp, &null_tech); } - ast_channel_internal_finalize(tmp); - - ast_atomic_fetchadd_int(&chancount, +1); - /* You might scream "locking inversion" at seeing this but it is actually perfectly fine. * Since the channel was just created nothing can know about it yet or even acquire it. */ ast_channel_lock(tmp); - ao2_link(channels, tmp); + ao2_lock(channels); + + if (assignedids && (does_id_conflict(assignedids->uniqueid) || does_id_conflict(assignedids->uniqueid2))) { + ast_channel_internal_errno_set(AST_CHANNEL_ERROR_ID_EXISTS); + ao2_unlock(channels); + /* This is a bit unorthodox, but we can't just call ast_channel_stage_snapshot_done() + * because that will result in attempting to publish the channel snapshot. That causes + * badness in some places, such as CDRs. So we need to manually clear the flag on the + * channel that says that a snapshot is being cleared. + */ + ast_clear_flag(ast_channel_flags(tmp), AST_FLAG_SNAPSHOT_STAGE); + ast_channel_unlock(tmp); + return ast_channel_unref(tmp); + } + + ast_channel_internal_finalize(tmp); + + ast_atomic_fetchadd_int(&chancount, +1); + + ao2_link_flags(channels, tmp, OBJ_NOLOCK); + + ao2_unlock(channels); if (endpoint) { ast_endpoint_add_channel(endpoint, tmp); @@ -10842,3 +10880,8 @@ int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridg { return channel_feature_hooks_set_full(chan, features, 1); } + +enum ast_channel_error ast_channel_errno(void) +{ + return ast_channel_internal_errno(); +} diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 51d49c29f02f6023ebc0132c9fb7f158a6815368..90f59d64a78bf657f6c5247217ce4ce12aaaec0f 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -1484,6 +1484,7 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags) struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *file, int line, const char *function) { struct ast_channel *tmp; + #if defined(REF_DEBUG) tmp = __ao2_alloc_debug(sizeof(*tmp), destructor, AO2_ALLOC_OPT_LOCK_MUTEX, "", file, line, function, 1); @@ -1675,3 +1676,25 @@ int ast_channel_internal_setup_topics(struct ast_channel *chan) return 0; } + +AST_THREADSTORAGE(channel_errno); + +void ast_channel_internal_errno_set(enum ast_channel_error error) +{ + enum ast_channel_error *error_code = ast_threadstorage_get(&channel_errno, sizeof(*error_code)); + if (!error_code) { + return; + } + + *error_code = error; +} + +enum ast_channel_error ast_channel_internal_errno(void) +{ + enum ast_channel_error *error_code = ast_threadstorage_get(&channel_errno, sizeof(*error_code)); + if (!error_code) { + return AST_CHANNEL_ERROR_UNKNOWN; + } + + return *error_code; +} diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index 6baac7a4e7cfacc7f71fa37ccd9004735b46c915..04db7045127547ba91088509f53d6f5edd961956 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -1109,7 +1109,12 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint, } if (ast_dial_prerun(dial, other, format_cap)) { - ast_ari_response_alloc_failed(response); + if (ast_channel_errno() == AST_CHANNEL_ERROR_ID_EXISTS) { + ast_ari_response_error(response, 409, "Conflict", + "Channel with given unique ID already exists"); + } else { + ast_ari_response_alloc_failed(response); + } ast_dial_destroy(dial); ast_free(origination); ast_channel_cleanup(other); diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index 9dc19cc86ea2da923207421cd94f7580d6938080..8cb3388fe654e3de5e0fa844269ea5505543780f 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -253,6 +253,7 @@ static void ast_ari_channels_originate_cb( case 500: /* Internal Server Error */ case 501: /* Not Implemented */ case 400: /* Invalid parameters for originating a channel. */ + case 409: /* Channel with given unique ID already exists. */ is_valid = 1; break; default: @@ -483,6 +484,7 @@ static void ast_ari_channels_originate_with_id_cb( case 500: /* Internal Server Error */ case 501: /* Not Implemented */ case 400: /* Invalid parameters for originating a channel. */ + case 409: /* Channel with given unique ID already exists. */ is_valid = 1; break; default: diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index 8eaa5eb9b5b81d9118172e0e229291e252c31f95..646604654bb37f773115f72f9e2a54688898528d 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -142,6 +142,10 @@ { "code": 400, "reason": "Invalid parameters for originating a channel." + }, + { + "code": 409, + "reason": "Channel with given unique ID already exists." } ] } @@ -298,6 +302,10 @@ { "code": 400, "reason": "Invalid parameters for originating a channel." + }, + { + "code": 409, + "reason": "Channel with given unique ID already exists." } ] },