diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index df752c90207c38a5c9c8e3451f1ecb0ce4ed3470..ff92cc8789241ebb65ec52680fa4876c253ff26f 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -4659,4 +4659,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 6804496f4128a4ce7fb933b3bf86e702146d18c2..4a9fe72a86a1b56f576f8f69d4fe12c22c3ceda0 100644 --- a/main/channel.c +++ b/main/channel.c @@ -770,6 +770,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))) @@ -945,16 +966,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); @@ -10866,3 +10904,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 d94b267e6806955cfec7527d0d1a51b800c072f3..3c156d4fa211556aa0f1ff1a622b730399ba979b 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -1661,3 +1661,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 8d32921298787055e6629398319e30f4bbbefdf2..b00b2378d6610d9c139a9a86da611372832a9167 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -1230,7 +1230,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 25da17d4d961ddf1e1e9bc4bb3553220276b83ac..b7c088c4d1a9ba2ecbd8d7d94c7b835d7daa15c9 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: @@ -615,6 +616,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 ee18bfe119683d5d563b4e1d797cce22b2f0cd73..60ace075d365d8098c9dc5827a81884c931a7a5a 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." } ] } @@ -368,6 +372,10 @@ { "code": 400, "reason": "Invalid parameters for originating a channel." + }, + { + "code": 409, + "reason": "Channel with given unique ID already exists." } ]