/* * Helper function to find channels. It supports these modes: * * prev != NULL : get channel next in list after prev * name != NULL : get channel with matching name * name != NULL && namelen != 0 : get channel whose name starts with prefix * exten != NULL : get channel whose exten or macroexten matches * context != NULL && exten != NULL : get channel whose context or macrocontext * * It returns with the channel's lock held. If getting the individual lock fails, * unlock and retry quickly up to 10 times, then give up. * * XXX Note that this code has cost O(N) because of the need to verify * that the object is still on the global list. * * XXX also note that accessing fields (e.g. c->name in opbx_log()) * can only be done with the lock held or someone could delete the * object while we work on it. This causes some ugliness in the code. * Note that removing the first opbx_log() may be harmful, as it would * shorten the retry period and possibly cause failures. * We should definitely go for a better scheme that is deadlock-free. */ static struct opbx_channel *channel_find_locked(const struct opbx_channel *prev, const char *name, const int namelen, const char *context, const char *exten) { const char *msg = prev ? "deadlock" : "initial deadlock"; int retries, done; struct opbx_channel *c; for (retries = 0; retries < 10; retries++) { opbx_mutex_lock(&chlock); for (c = channels; c; c = c->next) { if (!prev) { /* want head of list */ if (!name && !exten) break; if (name) { /* want match by full name */ if (!namelen) { if (!strcasecmp(c->name, name)) break; else continue; } /* want match by name prefix */ if (!strncasecmp(c->name, name, namelen)) break; } else if (exten) { /* want match by context and exten */ if (context && (strcasecmp(c->context, context) && strcasecmp(c->macrocontext, context))) continue; /* match by exten */ if (strcasecmp(c->exten, exten) && strcasecmp(c->macroexten, exten)) continue; else break; } } else if (c == prev) { /* found, return c->next */ c = c->next; break; } } /* exit if chan not found or mutex acquired successfully */ done = (c == NULL) || (opbx_mutex_trylock(&c->lock) == 0); /* this is slightly unsafe, as we _should_ hold the lock to access c->name */ if (!done && c) opbx_log(LOG_DEBUG, "Avoiding %s for '%s'\n", msg, c->name); opbx_mutex_unlock(&chlock); if (done) return c; usleep(1); } /* * c is surely not null, but we don't have the lock so cannot * access c->name */ opbx_log(LOG_WARNING, "Avoided %s for '%p', %d retries!\n", msg, c, retries); return NULL; }