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
00030
00031
00032
00033 #include "asterisk.h"
00034
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 403913 $")
00036
00037 #include <signal.h>
00038
00039 #include "asterisk/lock.h"
00040 #include "asterisk/file.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/module.h"
00044 #include "asterisk/linkedlists.h"
00045 #include "asterisk/astobj2.h"
00046 #include "asterisk/utils.h"
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110 static AST_LIST_HEAD_STATIC(locklist, lock_frame);
00111
00112 static void lock_free(void *data);
00113 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
00114 static int unloading = 0;
00115 static pthread_t broker_tid = AST_PTHREADT_NULL;
00116
00117 static const struct ast_datastore_info lock_info = {
00118 .type = "MUTEX",
00119 .destroy = lock_free,
00120 .chan_fixup = lock_fixup,
00121 };
00122
00123 struct lock_frame {
00124 AST_LIST_ENTRY(lock_frame) entries;
00125 ast_mutex_t mutex;
00126 ast_cond_t cond;
00127
00128 unsigned int count;
00129
00130 struct ao2_container *requesters;
00131
00132 struct ast_channel *owner;
00133
00134 char name[0];
00135 };
00136
00137 struct channel_lock_frame {
00138 AST_LIST_ENTRY(channel_lock_frame) list;
00139
00140 struct ast_channel *channel;
00141 struct lock_frame *lock_frame;
00142 };
00143
00144 static void lock_free(void *data)
00145 {
00146 AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
00147 struct channel_lock_frame *clframe;
00148 AST_LIST_LOCK(oldlist);
00149 while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
00150
00151 if (clframe->channel == clframe->lock_frame->owner) {
00152 clframe->lock_frame->count = 0;
00153 clframe->lock_frame->owner = NULL;
00154 }
00155 ast_free(clframe);
00156 }
00157 AST_LIST_UNLOCK(oldlist);
00158 AST_LIST_HEAD_DESTROY(oldlist);
00159 ast_free(oldlist);
00160 }
00161
00162 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
00163 {
00164 struct ast_datastore *lock_store = ast_channel_datastore_find(oldchan, &lock_info, NULL);
00165 AST_LIST_HEAD(, channel_lock_frame) *list;
00166 struct channel_lock_frame *clframe = NULL;
00167
00168 if (!lock_store) {
00169 return;
00170 }
00171 list = lock_store->data;
00172
00173 AST_LIST_LOCK(list);
00174 AST_LIST_TRAVERSE(list, clframe, list) {
00175 if (clframe->lock_frame->owner == oldchan) {
00176 clframe->lock_frame->owner = newchan;
00177 }
00178
00179 clframe->channel = newchan;
00180 }
00181 AST_LIST_UNLOCK(list);
00182 }
00183
00184 static void *lock_broker(void *unused)
00185 {
00186 struct lock_frame *frame;
00187 struct timespec forever = { 1000000, 0 };
00188 for (;;) {
00189 int found_requester = 0;
00190
00191
00192 pthread_testcancel();
00193 AST_LIST_LOCK(&locklist);
00194
00195 AST_LIST_TRAVERSE(&locklist, frame, entries) {
00196 if (ao2_container_count(frame->requesters)) {
00197 found_requester++;
00198 ast_mutex_lock(&frame->mutex);
00199 if (!frame->owner) {
00200 ast_cond_signal(&frame->cond);
00201 }
00202 ast_mutex_unlock(&frame->mutex);
00203 }
00204 }
00205
00206 AST_LIST_UNLOCK(&locklist);
00207 pthread_testcancel();
00208
00209
00210 if (!found_requester) {
00211 nanosleep(&forever, NULL);
00212 } else {
00213 sched_yield();
00214 }
00215 }
00216
00217 return NULL;
00218 }
00219
00220 static int ast_channel_hash_cb(const void *obj, const int flags)
00221 {
00222 const struct ast_channel *chan = obj;
00223 return ast_str_case_hash(chan->name);
00224 }
00225
00226 static int ast_channel_cmp_cb(void *obj, void *arg, int flags)
00227 {
00228 struct ast_channel *chan = obj, *cmp_args = arg;
00229 return strcasecmp(chan->name, cmp_args->name) ? 0 : CMP_MATCH;
00230 }
00231
00232 static int get_lock(struct ast_channel *chan, char *lockname, int trylock)
00233 {
00234 struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00235 struct lock_frame *current;
00236 struct channel_lock_frame *clframe = NULL;
00237 AST_LIST_HEAD(, channel_lock_frame) *list;
00238 int res = 0;
00239 struct timespec timeout = { 0, };
00240 struct timeval now;
00241
00242 if (!lock_store) {
00243 ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
00244 lock_store = ast_datastore_alloc(&lock_info, NULL);
00245 if (!lock_store) {
00246 ast_log(LOG_ERROR, "Unable to allocate new datastore. No locks will be obtained.\n");
00247 return -1;
00248 }
00249
00250 list = ast_calloc(1, sizeof(*list));
00251 if (!list) {
00252 ast_log(LOG_ERROR,
00253 "Unable to allocate datastore list head. %sLOCK will fail.\n",
00254 trylock ? "TRY" : "");
00255 ast_datastore_free(lock_store);
00256 return -1;
00257 }
00258
00259 lock_store->data = list;
00260 AST_LIST_HEAD_INIT(list);
00261 ast_channel_datastore_add(chan, lock_store);
00262 } else
00263 list = lock_store->data;
00264
00265
00266 AST_LIST_LOCK(&locklist);
00267 AST_LIST_TRAVERSE(&locklist, current, entries) {
00268 if (strcmp(current->name, lockname) == 0) {
00269 break;
00270 }
00271 }
00272
00273 if (!current) {
00274 if (unloading) {
00275
00276 AST_LIST_UNLOCK(&locklist);
00277 return -1;
00278 }
00279
00280
00281 current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
00282 if (!current) {
00283 AST_LIST_UNLOCK(&locklist);
00284 return -1;
00285 }
00286
00287 strcpy(current->name, lockname);
00288 if ((res = ast_mutex_init(¤t->mutex))) {
00289 ast_log(LOG_ERROR, "Unable to initialize mutex: %s\n", strerror(res));
00290 ast_free(current);
00291 AST_LIST_UNLOCK(&locklist);
00292 return -1;
00293 }
00294 if ((res = ast_cond_init(¤t->cond, NULL))) {
00295 ast_log(LOG_ERROR, "Unable to initialize condition variable: %s\n", strerror(res));
00296 ast_mutex_destroy(¤t->mutex);
00297 ast_free(current);
00298 AST_LIST_UNLOCK(&locklist);
00299 return -1;
00300 }
00301 if (!(current->requesters = ao2_container_alloc(1, ast_channel_hash_cb, ast_channel_cmp_cb))) {
00302 ast_mutex_destroy(¤t->mutex);
00303 ast_cond_destroy(¤t->cond);
00304 ast_free(current);
00305 AST_LIST_UNLOCK(&locklist);
00306 return -1;
00307 }
00308 AST_LIST_INSERT_TAIL(&locklist, current, entries);
00309 }
00310 AST_LIST_UNLOCK(&locklist);
00311
00312
00313 AST_LIST_LOCK(list);
00314 AST_LIST_TRAVERSE(list, clframe, list) {
00315 if (clframe->lock_frame == current) {
00316 break;
00317 }
00318 }
00319
00320 if (!clframe) {
00321 if (unloading) {
00322
00323 AST_LIST_UNLOCK(list);
00324 return -1;
00325 }
00326
00327 if (!(clframe = ast_calloc(1, sizeof(*clframe)))) {
00328 ast_log(LOG_ERROR,
00329 "Unable to allocate channel lock frame. %sLOCK will fail.\n",
00330 trylock ? "TRY" : "");
00331 AST_LIST_UNLOCK(list);
00332 return -1;
00333 }
00334
00335 clframe->lock_frame = current;
00336 clframe->channel = chan;
00337 AST_LIST_INSERT_TAIL(list, clframe, list);
00338 }
00339 AST_LIST_UNLOCK(list);
00340
00341
00342
00343
00344
00345 if (current->owner == chan) {
00346 current->count++;
00347 return 0;
00348 }
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361 AST_LIST_LOCK(&locklist);
00362 ast_mutex_lock(¤t->mutex);
00363
00364 ao2_link(current->requesters, chan);
00365 pthread_kill(broker_tid, SIGURG);
00366 AST_LIST_UNLOCK(&locklist);
00367
00368
00369 now = ast_tvnow();
00370 timeout.tv_sec = now.tv_sec + 3;
00371 timeout.tv_nsec = now.tv_usec * 1000;
00372
00373 if (!current->owner
00374 || (!trylock
00375 && !(res = ast_cond_timedwait(¤t->cond, ¤t->mutex, &timeout)))) {
00376 res = 0;
00377 current->owner = chan;
00378 current->count++;
00379 } else {
00380 res = -1;
00381 }
00382
00383 ao2_unlink(current->requesters, chan);
00384 ast_mutex_unlock(¤t->mutex);
00385
00386 return res;
00387 }
00388
00389 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00390 {
00391 struct ast_datastore *lock_store;
00392 struct channel_lock_frame *clframe;
00393 AST_LIST_HEAD(, channel_lock_frame) *list;
00394
00395 if (!chan) {
00396 return -1;
00397 }
00398
00399 lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00400 if (!lock_store) {
00401 ast_log(LOG_WARNING, "No datastore for dialplan locks. Nothing was ever locked!\n");
00402 ast_copy_string(buf, "0", len);
00403 return 0;
00404 }
00405
00406 if (!(list = lock_store->data)) {
00407 ast_debug(1, "This should NEVER happen\n");
00408 ast_copy_string(buf, "0", len);
00409 return 0;
00410 }
00411
00412
00413 AST_LIST_LOCK(list);
00414 AST_LIST_TRAVERSE(list, clframe, list) {
00415 if (clframe->lock_frame && clframe->lock_frame->owner == chan && strcmp(clframe->lock_frame->name, data) == 0) {
00416 break;
00417 }
00418 }
00419
00420
00421
00422 AST_LIST_UNLOCK(list);
00423
00424 if (!clframe) {
00425
00426 ast_copy_string(buf, "0", len);
00427 return 0;
00428 }
00429
00430 if (--clframe->lock_frame->count == 0) {
00431 clframe->lock_frame->owner = NULL;
00432 }
00433
00434 ast_copy_string(buf, "1", len);
00435 return 0;
00436 }
00437
00438 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00439 {
00440 if (!chan) {
00441 return -1;
00442 }
00443 ast_autoservice_start(chan);
00444 ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
00445 ast_autoservice_stop(chan);
00446
00447 return 0;
00448 }
00449
00450 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00451 {
00452 if (!chan) {
00453 return -1;
00454 }
00455 ast_autoservice_start(chan);
00456 ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
00457 ast_autoservice_stop(chan);
00458
00459 return 0;
00460 }
00461
00462 static struct ast_custom_function lock_function = {
00463 .name = "LOCK",
00464 .read = lock_read,
00465 .read_max = 2,
00466 };
00467
00468 static struct ast_custom_function trylock_function = {
00469 .name = "TRYLOCK",
00470 .read = trylock_read,
00471 .read_max = 2,
00472 };
00473
00474 static struct ast_custom_function unlock_function = {
00475 .name = "UNLOCK",
00476 .read = unlock_read,
00477 .read_max = 2,
00478 };
00479
00480 static int unload_module(void)
00481 {
00482 struct lock_frame *current;
00483
00484
00485 unloading = 1;
00486
00487 AST_LIST_LOCK(&locklist);
00488 while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
00489
00490 if (current->owner || ao2_container_count(current->requesters)) {
00491
00492 AST_LIST_INSERT_HEAD(&locklist, current, entries);
00493 AST_LIST_UNLOCK(&locklist);
00494 unloading = 0;
00495 return -1;
00496 }
00497 ast_mutex_destroy(¤t->mutex);
00498 ao2_ref(current->requesters, -1);
00499 ast_free(current);
00500 }
00501
00502
00503 ast_custom_function_unregister(&lock_function);
00504 ast_custom_function_unregister(&trylock_function);
00505 ast_custom_function_unregister(&unlock_function);
00506
00507 if (broker_tid != AST_PTHREADT_NULL) {
00508 pthread_cancel(broker_tid);
00509 pthread_kill(broker_tid, SIGURG);
00510 pthread_join(broker_tid, NULL);
00511 }
00512
00513 AST_LIST_UNLOCK(&locklist);
00514
00515 return 0;
00516 }
00517
00518 static int load_module(void)
00519 {
00520 int res = ast_custom_function_register_escalating(&lock_function, AST_CFE_READ);
00521 res |= ast_custom_function_register_escalating(&trylock_function, AST_CFE_READ);
00522 res |= ast_custom_function_register_escalating(&unlock_function, AST_CFE_READ);
00523
00524 if (ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL)) {
00525 ast_log(LOG_ERROR, "Failed to start lock broker thread. Unloading func_lock module.\n");
00526 broker_tid = AST_PTHREADT_NULL;
00527 unload_module();
00528 return AST_MODULE_LOAD_DECLINE;
00529 }
00530
00531 return res;
00532 }
00533
00534 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");