simplify locking (use a single lock, no deadlock possible)
[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 static pthread_cond_t change_pending;
48
49 int ast_parse_device_state(const char *device)
50 {
51         struct ast_channel *chan;
52         char match[AST_CHANNEL_NAME];
53         int res;
54
55         ast_copy_string(match, device, sizeof(match)-1);
56         strcat(match, "-");
57         chan = ast_get_channel_by_name_prefix_locked(match, strlen(match));
58
59         if (!chan)
60                 return AST_DEVICE_UNKNOWN;
61
62         if (chan->_state == AST_STATE_RINGING)
63                 res = AST_DEVICE_RINGING;
64         else
65                 res = AST_DEVICE_INUSE;
66         
67         ast_mutex_unlock(&chan->lock);
68
69         return res;
70 }
71
72 int ast_device_state(const char *device)
73 {
74         char *buf;
75         char *tech;
76         char *number;
77         const struct ast_channel_tech *chan_tech;
78         int res = 0;
79         
80         buf = ast_strdupa(device);
81         tech = strsep(&buf, "/");
82         number = buf;
83         if (!number)
84             return AST_DEVICE_INVALID;
85                 
86         chan_tech = ast_get_channel_tech(tech);
87         if (!chan_tech)
88                 return AST_DEVICE_INVALID;
89
90         if (!chan_tech->devicestate) 
91                 return ast_parse_device_state(device);
92         else {
93                 res = chan_tech->devicestate(number);
94                 if (res == AST_DEVICE_UNKNOWN)
95                         return ast_parse_device_state(device);
96                 else
97                         return res;
98         }
99 }
100
101 /*--- ast_devstate_add: Add device state watcher */
102 int ast_devstate_add(ast_devstate_cb_type callback, void *data)
103 {
104         struct devstate_cb *devcb;
105
106         if (!callback)
107                 return -1;
108
109         devcb = calloc(1, sizeof(*devcb));
110         if (!devcb)
111                 return -1;
112
113         devcb->data = data;
114         devcb->callback = callback;
115
116         AST_LIST_LOCK(&devstate_cbs);
117         AST_LIST_INSERT_HEAD(&devstate_cbs, devcb, list);
118         AST_LIST_UNLOCK(&devstate_cbs);
119
120         return 0;
121 }
122
123 /*--- ast_devstate_del: Remove device state watcher */
124 void ast_devstate_del(ast_devstate_cb_type callback, void *data)
125 {
126         struct devstate_cb *devcb;
127
128         AST_LIST_LOCK(&devstate_cbs);
129         AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) {
130                 if ((devcb->callback == callback) && (devcb->data == data)) {
131                         AST_LIST_REMOVE_CURRENT(&devstate_cbs, list);
132                         free(devcb);
133                         break;
134                 }
135         }
136         AST_LIST_TRAVERSE_SAFE_END;
137         AST_LIST_UNLOCK(&devstate_cbs);
138 }
139
140 /*--- do_state_change: Notify callback watchers of change, and notify PBX core for hint updates */
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 int ast_device_state_changed(const char *fmt, ...) 
159 {
160         char buf[AST_MAX_EXTENSION];
161         char *device;
162         char *parse;
163         struct state_change *change = NULL;
164         va_list ap;
165
166         va_start(ap, fmt);
167         vsnprintf(buf, sizeof(buf), fmt, ap);
168         va_end(ap);
169
170         parse = buf;
171         device = strsep(&parse, "-");
172
173         if (change_thread != AST_PTHREADT_NULL)
174                 change = calloc(1, sizeof(*change) + strlen(device));
175
176         if (!change) {
177                 /* we could not allocate a change struct, or */
178                 /* there is no background thread, so process the change now */
179                 do_state_change(device);
180         } else {
181                 /* queue the change */
182                 strcpy(change->device, device);
183                 AST_LIST_LOCK(&state_changes);
184                 AST_LIST_INSERT_TAIL(&state_changes, change, list);
185                 if (AST_LIST_FIRST(&state_changes) == change)
186                         /* the list was empty, signal the thread */
187                         pthread_cond_signal(&change_pending);
188                 AST_LIST_UNLOCK(&state_changes);
189         }
190
191         return 1;
192 }
193
194 static void *do_changes(void *data)
195 {
196         struct state_change *cur;
197
198         AST_LIST_LOCK(&state_changes);
199         for(;;) {
200                 /* the list lock will _always_ be held at this point in the loop */
201                 cur = AST_LIST_REMOVE_HEAD(&state_changes, list);
202                 if (cur) {
203                         /* we got an entry, so unlock the list while we process it */
204                         AST_LIST_UNLOCK(&state_changes);
205                         do_state_change(cur->device);
206                         free(cur);
207                         AST_LIST_LOCK(&state_changes);
208                 } else {
209                         /* there was no entry, so atomically unlock the list and wait for
210                            the condition to be signalled (returns with the lock held) */
211                         pthread_cond_wait(&change_pending, &state_changes.lock);
212                 }
213         }
214
215         return NULL;
216 }
217
218 int ast_device_state_engine_init(void)
219 {
220         pthread_attr_t attr;
221
222         pthread_cond_init(&change_pending, NULL);
223         pthread_attr_init(&attr);
224         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
225         if (ast_pthread_create(&change_thread, &attr, do_changes, NULL) < 0) {
226                 ast_log(LOG_ERROR, "Unable to start device state change thread.\n");
227                 return -1;
228         }
229
230         return 0;
231 }