Add some documentation detailing an aspect of dialplan functions, as requested by...
[asterisk/asterisk.git] / funcs / func_lock.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Tilghman Lesher
5  *
6  * Tilghman Lesher <func_lock_2007@the-tilghman.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Dialplan mutexes
22  *
23  * \author Tilghman Lesher <func_lock_2007@the-tilghman.com>
24  *
25  * \ingroup functions
26  * 
27  */
28
29 #include "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/options.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/options.h"
45 #include "asterisk/linkedlists.h"
46
47 AST_LIST_HEAD_STATIC(locklist, lock_frame);
48
49 static void lock_free(void *data);
50 static int unloading = 0;
51
52 static struct ast_datastore_info lock_info = {
53         .type = "MUTEX",
54         .destroy = lock_free,
55 };
56
57 struct lock_frame {
58         AST_LIST_ENTRY(lock_frame) entries;
59         ast_mutex_t mutex;
60         struct ast_channel *channel;
61         char name[0];
62 };
63
64 static void lock_free(void *data)
65 {
66         struct lock_frame *frame = data;
67         if (!frame)
68                 return;
69         frame->channel = NULL;
70         ast_mutex_unlock(&frame->mutex);
71 }
72
73 static int get_lock(struct ast_channel *chan, char *lockname, int try)
74 {
75         struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
76         struct lock_frame *current;
77         int res;
78
79         if (!lock_store) {
80                 ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
81                 lock_store = ast_channel_datastore_alloc(&lock_info, NULL);
82                 if (!lock_store) {
83                         ast_log(LOG_ERROR, "Unable to allocate new datastore.  No locks will be obtained.\n");
84                         return -1;
85                 }
86                 ast_channel_datastore_add(chan, lock_store);
87         }
88
89         /* If the channel already has a lock, then free the existing lock */
90         if (lock_store->data) {
91                 struct lock_frame *old = lock_store->data;
92                 old->channel = NULL;
93                 ast_mutex_unlock(&old->mutex);
94         }
95
96         /* Lock already exist? */
97         AST_LIST_LOCK(&locklist);
98         AST_LIST_TRAVERSE(&locklist, current, entries) {
99                 if (strcmp(current->name, lockname) == 0) {
100                         break;
101                 }
102         }
103
104         if (!current) {
105                 if (unloading) {
106                         /* Don't bother */
107                         AST_LIST_UNLOCK(&locklist);
108                         return -1;
109                 }
110
111                 /* Create new lock entry */
112                 current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
113                 if (!current) {
114                         AST_LIST_UNLOCK(&locklist);
115                         return -1;
116                 }
117
118                 strcpy((char *)current + sizeof(*current), lockname);
119                 ast_mutex_init(&current->mutex);
120                 AST_LIST_INSERT_TAIL(&locklist, current, entries);
121         }
122
123         res = try ? ast_mutex_trylock(&current->mutex) : ast_mutex_lock(&current->mutex);
124         if (res == 0) {
125                 lock_store->data = current;
126                 current->channel = chan;
127         }
128
129         AST_LIST_UNLOCK(&locklist);
130         return res;
131 }
132
133 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
134 {
135         struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
136         struct lock_frame *current;
137
138         if (!lock_store) {
139                 ast_log(LOG_WARNING, "No datastore for dialplan locks.  Nothing was ever locked!\n");
140                 ast_copy_string(buf, "0", len);
141                 return 0;
142         }
143
144         current = lock_store->data;
145
146         if (!current) {
147                 ast_copy_string(buf, "0", len);
148                 return 0;
149         }
150
151         current->channel = NULL;
152         ast_mutex_unlock(&current->mutex);
153         ast_copy_string(buf, "1", len);
154         return 0;
155 }
156
157 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
158 {
159         ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
160         return 0;
161 }
162
163 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
164 {
165         ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
166         return 0;
167 }
168
169 static struct ast_custom_function lock_function = {
170         .name = "LOCK",
171         .synopsis = "Attempt to obtain a named mutex",
172         .desc =
173 "Attempts to grab a named lock exclusively, and prevents other channels\n"
174 "from obtaining the same lock.  LOCK will wait for the lock to become\n"
175 "available.  Returns 1 if the lock was obtained or 0 on error.\n\n"
176 "Note: to avoid the possibility of a deadlock, LOCK will only attempt to\n"
177 "grab a single lock.  If you have a lock already and you attempt to lock\n"
178 "another name, LOCK will unlock the first name before attempting to lock\n"
179 "the second name.\n",
180         .syntax = "LOCK(<lockname>)",
181         .read = lock_read,
182 };
183
184 static struct ast_custom_function trylock_function = {
185         .name = "TRYLOCK",
186         .synopsis = "Attempt to obtain a named mutex",
187         .desc =
188 "Attempts to grab a named lock exclusively, and prevents other channels\n"
189 "from obtaining the same lock.  Returns 1 if the lock was available or 0\n"
190 "otherwise.\n\n"
191 "Note: to avoid the possibility of a deadlock, TRYLOCK will only attempt to\n"
192 "grab a single lock.  If you have a lock already and you attempt to lock\n"
193 "another name, TRYLOCK will unlock the first name before attempting to lock\n"
194 "the second name.\n",
195         .syntax = "TRYLOCK(<lockname>)",
196         .read = trylock_read,
197 };
198
199 static struct ast_custom_function unlock_function = {
200         .name = "UNLOCK",
201         .synopsis = "Unlocks a named mutex",
202         .desc =
203 "Unlocks a previously locked mutex.  Note that it is generally unnecessary to\n"
204 "unlock in a hangup routine, as any lock held is automatically freed when the\n"
205 "channel is destroyed.  Returns 1 if the channel had a lock or 0 otherwise.\n",
206         .syntax = "UNLOCK()",
207         .read = unlock_read,
208 };
209
210 static int unload_module(void)
211 {
212         struct lock_frame *current;
213
214         /* Module flag */
215         unloading = 1;
216
217         AST_LIST_LOCK(&locklist);
218         while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
219                 /* If any locks are currently in use, then we cannot unload this module */
220                 if (current->channel) {
221                         /* Put it back */
222                         AST_LIST_INSERT_HEAD(&locklist, current, entries);
223                         AST_LIST_UNLOCK(&locklist);
224                         unloading = 0;
225                         return -1;
226                 }
227                 ast_mutex_destroy(&current->mutex);
228                 ast_free(current);
229         }
230
231         /* No locks left, unregister functions */
232         ast_custom_function_unregister(&lock_function);
233         ast_custom_function_unregister(&trylock_function);
234         ast_custom_function_unregister(&unlock_function);
235
236         AST_LIST_UNLOCK(&locklist);
237         return 0;
238 }
239
240 static int load_module(void)
241 {
242         int res = ast_custom_function_register(&lock_function);
243         res |= ast_custom_function_register(&trylock_function);
244         res |= ast_custom_function_register(&unlock_function);
245         return res;
246 }
247
248 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");