00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 #include "asterisk.h"
00030
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 232014 $")
00032
00033 #include <signal.h>
00034
00035 #include "asterisk/lock.h"
00036 #include "asterisk/file.h"
00037 #include "asterisk/channel.h"
00038 #include "asterisk/pbx.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/linkedlists.h"
00041 #include "asterisk/astobj2.h"
00042 #include "asterisk/utils.h"
00043
00044 AST_LIST_HEAD_STATIC(locklist, lock_frame);
00045
00046 static void lock_free(void *data);
00047 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
00048 static int unloading = 0;
00049 static pthread_t broker_tid = AST_PTHREADT_NULL;
00050
00051 static struct ast_datastore_info lock_info = {
00052 .type = "MUTEX",
00053 .destroy = lock_free,
00054 .chan_fixup = lock_fixup,
00055 };
00056
00057 struct lock_frame {
00058 AST_LIST_ENTRY(lock_frame) entries;
00059 ast_mutex_t mutex;
00060 ast_cond_t cond;
00061
00062 unsigned int count;
00063
00064 struct ao2_container *requesters;
00065
00066 struct ast_channel *owner;
00067
00068 char name[0];
00069 };
00070
00071 struct channel_lock_frame {
00072 AST_LIST_ENTRY(channel_lock_frame) list;
00073
00074 struct ast_channel *channel;
00075 struct lock_frame *lock_frame;
00076 };
00077
00078 static void lock_free(void *data)
00079 {
00080 AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
00081 struct channel_lock_frame *clframe;
00082 AST_LIST_LOCK(oldlist);
00083 while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
00084
00085 if (clframe->channel == clframe->lock_frame->owner) {
00086 clframe->lock_frame->count = 0;
00087 clframe->lock_frame->owner = NULL;
00088 }
00089 ast_free(clframe);
00090 }
00091 AST_LIST_UNLOCK(oldlist);
00092 AST_LIST_HEAD_DESTROY(oldlist);
00093 ast_free(oldlist);
00094 }
00095
00096 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
00097 {
00098 struct ast_datastore *lock_store = ast_channel_datastore_find(oldchan, &lock_info, NULL);
00099 AST_LIST_HEAD(, channel_lock_frame) *list;
00100 struct channel_lock_frame *clframe = NULL;
00101
00102 if (!lock_store) {
00103 return;
00104 }
00105 list = lock_store->data;
00106
00107 AST_LIST_LOCK(list);
00108 AST_LIST_TRAVERSE(list, clframe, list) {
00109 if (clframe->lock_frame->owner == oldchan) {
00110 clframe->lock_frame->owner = newchan;
00111 }
00112
00113 clframe->channel = newchan;
00114 }
00115 AST_LIST_UNLOCK(list);
00116 }
00117
00118 static void *lock_broker(void *unused)
00119 {
00120 struct lock_frame *frame;
00121 struct timespec forever = { 1000000, 0 };
00122 for (;;) {
00123 int found_requester = 0;
00124
00125
00126 pthread_testcancel();
00127 AST_LIST_LOCK(&locklist);
00128
00129 AST_LIST_TRAVERSE(&locklist, frame, entries) {
00130 if (ao2_container_count(frame->requesters)) {
00131 found_requester++;
00132 ast_mutex_lock(&frame->mutex);
00133 if (!frame->owner) {
00134 ast_cond_signal(&frame->cond);
00135 }
00136 ast_mutex_unlock(&frame->mutex);
00137 }
00138 }
00139
00140 AST_LIST_UNLOCK(&locklist);
00141 pthread_testcancel();
00142
00143
00144 if (!found_requester) {
00145 nanosleep(&forever, NULL);
00146 } else {
00147 sched_yield();
00148 }
00149 }
00150
00151 return NULL;
00152 }
00153
00154 static int null_hash_cb(const void *obj, const int flags)
00155 {
00156 return (int)(long) obj;
00157 }
00158
00159 static int null_cmp_cb(void *obj, void *arg, int flags)
00160 {
00161 return obj == arg ? CMP_MATCH : 0;
00162 }
00163
00164 static int get_lock(struct ast_channel *chan, char *lockname, int try)
00165 {
00166 struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00167 struct lock_frame *current;
00168 struct channel_lock_frame *clframe = NULL;
00169 AST_LIST_HEAD(, channel_lock_frame) *list;
00170 int res = 0, *link;
00171 struct timespec three_seconds = { .tv_sec = 3 };
00172
00173 if (!lock_store) {
00174 ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
00175 lock_store = ast_datastore_alloc(&lock_info, NULL);
00176 if (!lock_store) {
00177 ast_log(LOG_ERROR, "Unable to allocate new datastore. No locks will be obtained.\n");
00178 return -1;
00179 }
00180
00181 list = ast_calloc(1, sizeof(*list));
00182 if (!list) {
00183 ast_log(LOG_ERROR, "Unable to allocate datastore list head. %sLOCK will fail.\n", try ? "TRY" : "");
00184 ast_datastore_free(lock_store);
00185 return -1;
00186 }
00187
00188 lock_store->data = list;
00189 AST_LIST_HEAD_INIT(list);
00190 ast_channel_datastore_add(chan, lock_store);
00191 } else
00192 list = lock_store->data;
00193
00194
00195 AST_LIST_LOCK(&locklist);
00196 AST_LIST_TRAVERSE(&locklist, current, entries) {
00197 if (strcmp(current->name, lockname) == 0) {
00198 break;
00199 }
00200 }
00201
00202 if (!current) {
00203 if (unloading) {
00204
00205 AST_LIST_UNLOCK(&locklist);
00206 return -1;
00207 }
00208
00209
00210 current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
00211 if (!current) {
00212 AST_LIST_UNLOCK(&locklist);
00213 return -1;
00214 }
00215
00216 strcpy(current->name, lockname);
00217 if ((res = ast_mutex_init(¤t->mutex))) {
00218 ast_log(LOG_ERROR, "Unable to initialize mutex: %s\n", strerror(res));
00219 ast_free(current);
00220 AST_LIST_UNLOCK(&locklist);
00221 return -1;
00222 }
00223 if ((res = ast_cond_init(¤t->cond, NULL))) {
00224 ast_log(LOG_ERROR, "Unable to initialize condition variable: %s\n", strerror(res));
00225 ast_mutex_destroy(¤t->mutex);
00226 ast_free(current);
00227 AST_LIST_UNLOCK(&locklist);
00228 return -1;
00229 }
00230 if (!(current->requesters = ao2_container_alloc(7, null_hash_cb, null_cmp_cb))) {
00231 ast_mutex_destroy(¤t->mutex);
00232 ast_cond_destroy(¤t->cond);
00233 ast_free(current);
00234 AST_LIST_UNLOCK(&locklist);
00235 return -1;
00236 }
00237 AST_LIST_INSERT_TAIL(&locklist, current, entries);
00238 }
00239 AST_LIST_UNLOCK(&locklist);
00240
00241
00242 AST_LIST_LOCK(list);
00243 AST_LIST_TRAVERSE(list, clframe, list) {
00244 if (clframe->lock_frame == current) {
00245 break;
00246 }
00247 }
00248
00249 if (!clframe) {
00250 if (unloading) {
00251
00252 AST_LIST_UNLOCK(list);
00253 return -1;
00254 }
00255
00256 if (!(clframe = ast_calloc(1, sizeof(*clframe)))) {
00257 ast_log(LOG_ERROR, "Unable to allocate channel lock frame. %sLOCK will fail.\n", try ? "TRY" : "");
00258 AST_LIST_UNLOCK(list);
00259 return -1;
00260 }
00261
00262 clframe->lock_frame = current;
00263 clframe->channel = chan;
00264 AST_LIST_INSERT_TAIL(list, clframe, list);
00265 }
00266 AST_LIST_UNLOCK(list);
00267
00268
00269
00270
00271
00272 if (current->owner == chan) {
00273 current->count++;
00274 return 0;
00275 }
00276
00277
00278
00279 if (!(link = ao2_alloc(sizeof(*link), NULL))) {
00280 return -1;
00281 }
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294 AST_LIST_LOCK(&locklist);
00295 ast_mutex_lock(¤t->mutex);
00296
00297 ao2_link(current->requesters, link);
00298 pthread_kill(broker_tid, SIGURG);
00299 AST_LIST_UNLOCK(&locklist);
00300
00301 if ((!current->owner) ||
00302 (!try && !(res = ast_cond_timedwait(¤t->cond, ¤t->mutex, &three_seconds)))) {
00303 res = 0;
00304 current->owner = chan;
00305 current->count++;
00306 } else {
00307 res = -1;
00308 }
00309
00310 ao2_unlink(current->requesters, link);
00311 ao2_ref(link, -1);
00312 ast_mutex_unlock(¤t->mutex);
00313
00314 return res;
00315 }
00316
00317 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00318 {
00319 struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00320 struct channel_lock_frame *clframe;
00321 AST_LIST_HEAD(, channel_lock_frame) *list;
00322
00323 if (!lock_store) {
00324 ast_log(LOG_WARNING, "No datastore for dialplan locks. Nothing was ever locked!\n");
00325 ast_copy_string(buf, "0", len);
00326 return 0;
00327 }
00328
00329 if (!(list = lock_store->data)) {
00330 ast_debug(1, "This should NEVER happen\n");
00331 ast_copy_string(buf, "0", len);
00332 return 0;
00333 }
00334
00335
00336 AST_LIST_LOCK(list);
00337 AST_LIST_TRAVERSE(list, clframe, list) {
00338 if (clframe->lock_frame && clframe->lock_frame->owner == chan && strcmp(clframe->lock_frame->name, data) == 0) {
00339 break;
00340 }
00341 }
00342
00343
00344
00345 AST_LIST_UNLOCK(list);
00346
00347 if (!clframe) {
00348
00349 ast_copy_string(buf, "0", len);
00350 return 0;
00351 }
00352
00353 if (--clframe->lock_frame->count == 0) {
00354 clframe->lock_frame->owner = NULL;
00355 }
00356
00357 ast_copy_string(buf, "1", len);
00358 return 0;
00359 }
00360
00361 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00362 {
00363 if (chan)
00364 ast_autoservice_start(chan);
00365
00366 ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
00367
00368 if (chan)
00369 ast_autoservice_stop(chan);
00370
00371 return 0;
00372 }
00373
00374 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00375 {
00376 if (chan)
00377 ast_autoservice_start(chan);
00378
00379 ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
00380
00381 if (chan)
00382 ast_autoservice_stop(chan);
00383
00384 return 0;
00385 }
00386
00387 static struct ast_custom_function lock_function = {
00388 .name = "LOCK",
00389 .synopsis = "Attempt to obtain a named mutex",
00390 .desc =
00391 "Attempts to grab a named lock exclusively, and prevents other channels from\n"
00392 "obtaining the same lock. LOCK will wait for the lock to become available.\n"
00393 "Returns 1 if the lock was obtained or 0 on error.\n\n"
00394 "Note: to avoid the possibility of a deadlock, LOCK will only attempt to\n"
00395 "obtain the lock for 3 seconds if the channel already has another lock.\n",
00396 .syntax = "LOCK(<lockname>)",
00397 .read = lock_read,
00398 };
00399
00400 static struct ast_custom_function trylock_function = {
00401 .name = "TRYLOCK",
00402 .synopsis = "Attempt to obtain a named mutex",
00403 .desc =
00404 "Attempts to grab a named lock exclusively, and prevents other channels\n"
00405 "from obtaining the same lock. Returns 1 if the lock was available or 0\n"
00406 "otherwise.\n",
00407 .syntax = "TRYLOCK(<lockname>)",
00408 .read = trylock_read,
00409 };
00410
00411 static struct ast_custom_function unlock_function = {
00412 .name = "UNLOCK",
00413 .synopsis = "Unlocks a named mutex",
00414 .desc =
00415 "Unlocks a previously locked mutex. Note that it is generally unnecessary to\n"
00416 "unlock in a hangup routine, as any locks held are automatically freed when the\n"
00417 "channel is destroyed. Returns 1 if the channel had a lock or 0 otherwise.\n",
00418 .syntax = "UNLOCK(<lockname>)",
00419 .read = unlock_read,
00420 };
00421
00422 static int unload_module(void)
00423 {
00424 struct lock_frame *current;
00425
00426
00427 unloading = 1;
00428
00429 AST_LIST_LOCK(&locklist);
00430 while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
00431
00432 if (current->owner || ao2_container_count(current->requesters)) {
00433
00434 AST_LIST_INSERT_HEAD(&locklist, current, entries);
00435 AST_LIST_UNLOCK(&locklist);
00436 unloading = 0;
00437 return -1;
00438 }
00439 ast_mutex_destroy(¤t->mutex);
00440 ao2_ref(current->requesters, -1);
00441 ast_free(current);
00442 }
00443
00444
00445 ast_custom_function_unregister(&lock_function);
00446 ast_custom_function_unregister(&trylock_function);
00447 ast_custom_function_unregister(&unlock_function);
00448
00449 pthread_cancel(broker_tid);
00450 pthread_kill(broker_tid, SIGURG);
00451 pthread_join(broker_tid, NULL);
00452
00453 AST_LIST_UNLOCK(&locklist);
00454
00455 return 0;
00456 }
00457
00458 static int load_module(void)
00459 {
00460 int res = ast_custom_function_register(&lock_function);
00461 res |= ast_custom_function_register(&trylock_function);
00462 res |= ast_custom_function_register(&unlock_function);
00463 ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL);
00464 return res;
00465 }
00466
00467 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");