merging the zap_and_dahdi_trunk branch up to trunk
[asterisk/asterisk.git] / apps / app_dahdiscan.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Modified from app_zapbarge by David Troy <dave@toad.net>
9  *
10  * Special thanks to comphealth.com for sponsoring this
11  * GPL application.
12  *
13  * See http://www.asterisk.org for more information about
14  * the Asterisk project. Please do not directly contact
15  * any of the maintainers of this project for assistance;
16  * the project provides a web site, mailing lists and IRC
17  * channels for your use.
18  *
19  * This program is free software, distributed under the terms of
20  * the GNU General Public License Version 2. See the LICENSE file
21  * at the top of the source tree.
22  */
23
24 /*! \file
25  *
26  * \brief DAHDI Scanner
27  *
28  * \author Mark Spencer <markster@digium.com>
29  *
30  * \ingroup applications
31  */
32
33 /*** MODULEINFO
34         <depend>dahdi</depend>
35  ***/
36
37 #include "asterisk.h"
38
39 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40
41 #include <dahdi/user.h>
42
43 #include "asterisk/lock.h"
44 #include "asterisk/file.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/config.h"
49 #include "asterisk/app.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/cli.h"
52 #include "asterisk/say.h"
53 #include "asterisk/options.h"
54
55 static char *app = "DAHDIScan";
56 static char *deprecated_app = "ZapScan";
57
58 static char *synopsis = "Scan DAHDI channels to monitor calls";
59
60 static char *descrip =
61 "  DAHDIScan([group]) allows a call center manager to monitor DAHDI channels in\n"
62 "a convenient way.  Use '#' to select the next channel and use '*' to exit\n"
63 "Limit scanning to a channel GROUP by setting the option group argument.\n";
64
65
66 #define CONF_SIZE 160
67
68 static struct ast_channel *get_dahdi_channel_locked(int num) {
69         char name[80];
70         
71         snprintf(name, sizeof(name), "DAHDI/%d-1", num);
72         return ast_get_channel_by_name_locked(name);
73 }
74
75 static int careful_write(int fd, unsigned char *data, int len)
76 {
77         int res;
78         while (len) {
79                 res = write(fd, data, len);
80                 if (res < 1) {
81                         if (errno != EAGAIN) {
82                                 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
83                                 return -1;
84                         } else {
85                                 return 0;
86                         }
87                 }
88                 len -= res;
89                 data += res;
90         }
91         return 0;
92 }
93
94 static int conf_run(struct ast_channel *chan, int confno, int confflags)
95 {
96         int fd;
97         struct dahdi_confinfo dahdic;
98         struct ast_frame *f;
99         struct ast_channel *c;
100         struct ast_frame fr;
101         int outfd;
102         int ms;
103         int nfds;
104         int res;
105         int flags;
106         int retrydahdi;
107         int origfd;
108         int ret = -1;
109         char input[4];
110         int ic = 0;
111         
112         DAHDI_BUFFERINFO bi;
113         char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
114         char *buf = __buf + AST_FRIENDLY_OFFSET;
115         
116         /* Set it into U-law mode (write) */
117         if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
118                 ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
119                 goto outrun;
120         }
121         
122         /* Set it into U-law mode (read) */
123         if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
124                 ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
125                 goto outrun;
126         }
127         ast_indicate(chan, -1);
128         retrydahdi = strcasecmp(chan->tech->type, "DAHDI");
129  dahdiretry:
130         origfd = chan->fds[0];
131         if (retrydahdi) {
132                 fd = open("/dev/dahdi/pseudo", O_RDWR);
133                 if (fd < 0) {
134                         ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
135                         goto outrun;
136                 }
137                 /* Make non-blocking */
138                 flags = fcntl(fd, F_GETFL);
139                 if (flags < 0) {
140                         ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
141                         close(fd);
142                         goto outrun;
143                 }
144                 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
145                         ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
146                         close(fd);
147                         goto outrun;
148                 }
149                 /* Setup buffering information */
150                 memset(&bi, 0, sizeof(bi));
151                 bi.bufsize = CONF_SIZE;
152                 bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
153                 bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
154                 bi.numbufs = 4;
155                 if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
156                         ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
157                         close(fd);
158                         goto outrun;
159                 }
160                 nfds = 1;
161         } else {
162                 /* XXX Make sure we're not running on a pseudo channel XXX */
163                 fd = chan->fds[0];
164                 nfds = 0;
165         }
166         memset(&dahdic, 0, sizeof(dahdic));
167         /* Check to see if we're in a conference... */
168         dahdic.chan = 0;
169         if (ioctl(fd, DAHDI_GETCONF, &dahdic)) {
170                 ast_log(LOG_WARNING, "Error getting conference\n");
171                 close(fd);
172                 goto outrun;
173         }
174         if (dahdic.confmode) {
175                 /* Whoa, already in a conference...  Retry... */
176                 if (!retrydahdi) {
177                         ast_debug(1, "DAHDI channel is in a conference already, retrying with pseudo\n");
178                         retrydahdi = 1;
179                         goto dahdiretry;
180                 }
181         }
182         memset(&dahdic, 0, sizeof(dahdic));
183         /* Add us to the conference */
184         dahdic.chan = 0;
185         dahdic.confno = confno;
186         dahdic.confmode = DAHDI_CONF_MONITORBOTH;
187
188         if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
189                 ast_log(LOG_WARNING, "Error setting conference\n");
190                 close(fd);
191                 goto outrun;
192         }
193         ast_debug(1, "Placed channel %s in DAHDI channel %d monitor\n", chan->name, confno);
194
195         for (;;) {
196                 outfd = -1;
197                 ms = -1;
198                 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
199                 if (c) {
200                         if (c->fds[0] != origfd) {
201                                 if (retrydahdi) {
202                                         /* Kill old pseudo */
203                                         close(fd);
204                                 }
205                                 ast_debug(1, "Ooh, something swapped out under us, starting over\n");
206                                 retrydahdi = 0;
207                                 goto dahdiretry;
208                         }
209                         f = ast_read(c);
210                         if (!f) {
211                                 break;
212                         }
213                         if (f->frametype == AST_FRAME_DTMF) {
214                                 if (f->subclass == '#') {
215                                         ret = 0;
216                                         break;
217                                 } else if (f->subclass == '*') {
218                                         ret = -1;
219                                         break;
220                                 } else {
221                                         input[ic++] = f->subclass;
222                                 }
223                                 if (ic == 3) {
224                                         input[ic++] = '\0';
225                                         ic = 0;
226                                         ret = atoi(input);
227                                         ast_verb(3, "DAHDIScan: change channel to %d\n", ret);
228                                         break;
229                                 }
230                         }
231
232                         if (fd != chan->fds[0]) {
233                                 if (f->frametype == AST_FRAME_VOICE) {
234                                         if (f->subclass == AST_FORMAT_ULAW) {
235                                                 /* Carefully write */
236                                                 careful_write(fd, f->data.ptr, f->datalen);
237                                         } else {
238                                                 ast_log(LOG_WARNING, "Huh?  Got a non-ulaw (%d) frame in the conference\n", f->subclass);
239                                         }
240                                 }
241                         }
242                         ast_frfree(f);
243                 } else if (outfd > -1) {
244                         res = read(outfd, buf, CONF_SIZE);
245                         if (res > 0) {
246                                 memset(&fr, 0, sizeof(fr));
247                                 fr.frametype = AST_FRAME_VOICE;
248                                 fr.subclass = AST_FORMAT_ULAW;
249                                 fr.datalen = res;
250                                 fr.samples = res;
251                                 fr.data.ptr = buf;
252                                 fr.offset = AST_FRIENDLY_OFFSET;
253                                 if (ast_write(chan, &fr) < 0) {
254                                         ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
255                                         /* break; */
256                                 }
257                         } else {
258                                 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
259                         }
260                 }
261         }
262         if (f) {
263                 ast_frfree(f);
264         }
265         if (fd != chan->fds[0]) {
266                 close(fd);
267         } else {
268                 /* Take out of conference */
269                 /* Add us to the conference */
270                 dahdic.chan = 0;
271                 dahdic.confno = 0;
272                 dahdic.confmode = 0;
273                 if (ioctl(fd, DAHDI_SETCONF, &dahdic)) {
274                         ast_log(LOG_WARNING, "Error setting conference\n");
275                 }
276         }
277
278  outrun:
279
280         return ret;
281 }
282
283 static int conf_exec(struct ast_channel *chan, void *data)
284 {
285         int res=-1;
286         int confflags = 0;
287         int confno = 0;
288         char confstr[80] = "", *tmp = NULL;
289         struct ast_channel *tempchan = NULL, *lastchan = NULL, *ichan = NULL;
290         struct ast_frame *f;
291         char *desired_group;
292         int input = 0, search_group = 0;
293
294         if (chan->_state != AST_STATE_UP)
295                 ast_answer(chan);
296
297         desired_group = ast_strdupa(data);
298         if (!ast_strlen_zero(desired_group)) {
299                 ast_verb(3, "Scanning for group %s\n", desired_group);
300                 search_group = 1;
301         }
302
303         for (;;) {
304                 if (ast_waitfor(chan, 100) < 0)
305                         break;
306
307                 f = ast_read(chan);
308                 if (!f)
309                         break;
310                 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) {
311                         ast_frfree(f);
312                         break;
313                 }
314                 ast_frfree(f);
315                 ichan = NULL;
316                 if(input) {
317                         ichan = get_dahdi_channel_locked(input);
318                         input = 0;
319                 }
320
321                 tempchan = ichan ? ichan : ast_channel_walk_locked(tempchan);
322
323                 if (!tempchan && !lastchan) {
324                         break;
325                 }
326
327                 if (tempchan && search_group) {
328                         const char *mygroup;
329                         if ((mygroup = pbx_builtin_getvar_helper(tempchan, "GROUP")) && (!strcmp(mygroup, desired_group))) {
330                                 ast_verb(3, "Found Matching Channel %s in group %s\n", tempchan->name, desired_group);
331                         } else {
332                                 ast_channel_unlock(tempchan);
333                                 lastchan = tempchan;
334                                 continue;
335                         }
336                 }
337                 if (tempchan && (!strcmp(tempchan->tech->type, "DAHDI")) && (tempchan != chan)) {
338                         ast_verb(3, "DAHDI channel %s is in-use, monitoring...\n", tempchan->name);
339                         ast_copy_string(confstr, tempchan->name, sizeof(confstr));
340                         ast_channel_unlock(tempchan);
341                         if ((tmp = strchr(confstr, '-'))) {
342                                 *tmp = '\0';
343                         }
344                         confno = atoi(strchr(confstr, '/') + 1);
345                         ast_stopstream(chan);
346                         ast_say_number(chan, confno, AST_DIGIT_ANY, chan->language, (char *) NULL);
347                         res = conf_run(chan, confno, confflags);
348                         if (res < 0) {
349                                 break;
350                         }
351                         input = res;
352                 } else if (tempchan) {
353                         ast_channel_unlock(tempchan);
354                 }
355                 lastchan = tempchan;
356         }
357         return res;
358 }
359
360 static int conf_exec_warn(struct ast_channel *chan, void *data)
361 {
362     ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", deprecated_app, app);
363     return conf_exec(chan, data);
364 }
365
366 static int unload_module(void)
367 {
368         return ast_unregister_application(app);
369 }
370
371 static int load_module(void)
372 {
373         ast_register_application(deprecated_app, conf_exec_warn, synopsis, descrip);
374         return ((ast_register_application(app, conf_exec, synopsis, descrip)) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS);
375 }
376
377 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Scan DAHDI channels application");
378