Fix lock ordering in devicestate
[asterisk/asterisk.git] / devicestate.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Device state management
5  * 
6  * Copyright (C) 2005, Digium, Inc.
7  *
8  * This program is free software, distributed under the terms of
9  * the GNU General Public License
10  */
11
12 #include <sys/types.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <string.h>
16
17 #include "asterisk.h"
18
19 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
20
21 #include "asterisk/channel.h"
22 #include "asterisk/utils.h"
23 #include "asterisk/lock.h"
24 #include "asterisk/linkedlists.h"
25 #include "asterisk/logger.h"
26 #include "asterisk/devicestate.h"
27 #include "asterisk/pbx.h"
28 #include "asterisk/options.h"
29
30 /* ast_devstate_cb: A device state watcher (callback) */
31 struct devstate_cb {
32         void *data;
33         ast_devstate_cb_type callback;
34         AST_LIST_ENTRY(devstate_cb) list;
35 };
36
37 static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb);
38
39 struct state_change {
40         AST_LIST_ENTRY(state_change) list;
41         char device[1];
42 };
43
44 static AST_LIST_HEAD_STATIC(state_changes, state_change);
45
46 static pthread_t change_thread = AST_PTHREADT_NULL;
47 AST_MUTEX_DEFINE_STATIC(change_pending_lock);
48 static pthread_cond_t change_pending;
49
50 int ast_parse_device_state(const char *device)
51 {
52         struct ast_channel *chan;
53         char match[AST_CHANNEL_NAME];
54         int res;
55
56         ast_copy_string(match, device, sizeof(match)-1);
57         strcat(match, "-");
58         chan = ast_get_channel_by_name_prefix_locked(match, strlen(match));
59
60         if (!chan)
61                 return AST_DEVICE_UNKNOWN;
62
63         if (chan->_state == AST_STATE_RINGING)
64                 res = AST_DEVICE_RINGING;
65         else
66                 res = AST_DEVICE_INUSE;
67         
68         ast_mutex_unlock(&chan->lock);
69
70         return res;
71 }
72
73 int ast_device_state(const char *device)
74 {
75         char *buf;
76         char *tech;
77         char *number;
78         const struct ast_channel_tech *chan_tech;
79         int res = 0;
80         
81         buf = ast_strdupa(device);
82         tech = strsep(&buf, "/");
83         number = buf;
84         if (!number)
85             return AST_DEVICE_INVALID;
86                 
87         chan_tech = ast_get_channel_tech(tech);
88         if (!chan_tech)
89                 return AST_DEVICE_INVALID;
90
91         if (!chan_tech->devicestate) 
92                 return ast_parse_device_state(device);
93         else {
94                 res = chan_tech->devicestate(number);
95                 if (res == AST_DEVICE_UNKNOWN)
96                         return ast_parse_device_state(device);
97                 else
98                         return res;
99         }
100 }
101
102 /*--- ast_devstate_add: Add device state watcher */
103 int ast_devstate_add(ast_devstate_cb_type callback, void *data)
104 {
105         struct devstate_cb *devcb;
106
107         if (!callback)
108                 return -1;
109
110         devcb = calloc(1, sizeof(*devcb));
111         if (!devcb)
112                 return -1;
113
114         devcb->data = data;
115         devcb->callback = callback;
116
117         AST_LIST_LOCK(&devstate_cbs);
118         AST_LIST_INSERT_HEAD(&devstate_cbs, devcb, list);
119         AST_LIST_UNLOCK(&devstate_cbs);
120
121         return 0;
122 }
123
124 /*--- ast_devstate_del: Remove device state watcher */
125 void ast_devstate_del(ast_devstate_cb_type callback, void *data)
126 {
127         struct devstate_cb *devcb;
128
129         AST_LIST_LOCK(&devstate_cbs);
130         AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) {
131                 if ((devcb->callback == callback) && (devcb->data == data)) {
132                         AST_LIST_REMOVE_CURRENT(&devstate_cbs, list);
133                         free(devcb);
134                         break;
135                 }
136         }
137         AST_LIST_TRAVERSE_SAFE_END;
138         AST_LIST_UNLOCK(&devstate_cbs);
139 }
140
141 static void do_state_change(const char *device)
142 {
143         int state;
144         struct devstate_cb *devcb;
145
146         state = ast_device_state(device);
147         if (option_debug > 2)
148                 ast_log(LOG_DEBUG, "Changing state for %s - state %d\n", device, state);
149
150         AST_LIST_LOCK(&devstate_cbs);
151         AST_LIST_TRAVERSE(&devstate_cbs, devcb, list)
152                 devcb->callback(device, state, devcb->data);
153         AST_LIST_UNLOCK(&devstate_cbs);
154
155         ast_hint_state_changed(device);
156 }
157
158 /*--- ast_device_state_changed: Notify callback watchers of change, and notify PBX core for hint updates */
159 int ast_device_state_changed(const char *fmt, ...) 
160 {
161         char buf[AST_MAX_EXTENSION];
162         char *device;
163         char *parse;
164         struct state_change *change = NULL;
165         va_list ap;
166
167         va_start(ap, fmt);
168         vsnprintf(buf, sizeof(buf), fmt, ap);
169         va_end(ap);
170
171         parse = buf;
172         device = strsep(&parse, "-");
173
174         if (change_thread != AST_PTHREADT_NULL)
175                 change = calloc(1, sizeof(*change) + strlen(device));
176
177         if (!change) {
178                 /* we could not allocate a change struct, or */
179                 /* there is no background thread, so process the change now */
180                 do_state_change(device);
181         } else {
182                 /* queue the change */
183                 strcpy(change->device, device);
184                 AST_LIST_LOCK(&state_changes);
185                 AST_LIST_INSERT_TAIL(&state_changes, change, list);
186                 if (AST_LIST_FIRST(&state_changes) == change) {
187                         AST_LIST_UNLOCK(&state_changes);
188                         /* the list was empty, signal the thread */
189                         ast_mutex_lock(&change_pending_lock);
190                         pthread_cond_signal(&change_pending);
191                         ast_mutex_unlock(&change_pending_lock);
192                 } else {
193                         AST_LIST_UNLOCK(&state_changes);
194                 }
195         }
196
197         return 1;
198 }
199
200 static void *do_changes(void *data)
201 {
202         struct state_change *cur;
203
204         for(;;) {
205                 ast_mutex_lock(&change_pending_lock);
206                 pthread_cond_wait(&change_pending, &change_pending_lock);
207                 for (;;) {
208                         AST_LIST_LOCK(&state_changes);
209                         cur = AST_LIST_REMOVE_HEAD(&state_changes, list);
210                         AST_LIST_UNLOCK(&state_changes);
211                         if (!cur)
212                                 break;
213
214                         do_state_change(cur->device);
215                         free(cur);
216                 }
217                 ast_mutex_unlock(&change_pending_lock);
218         }
219
220         return NULL;
221 }
222
223 int ast_device_state_engine_init(void)
224 {
225         pthread_attr_t attr;
226
227         pthread_cond_init(&change_pending, NULL);
228         pthread_attr_init(&attr);
229         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
230         if (ast_pthread_create(&change_thread, &attr, do_changes, NULL) < 0) {
231                 ast_log(LOG_ERROR, "Unable to start device state change thread.\n");
232                 return -1;
233         }
234
235         return 0;
236 }