9fd4e6d8a595a2342f00e18dc9a4076c64ce4788
[asterisk/asterisk.git] / io.c
1 /*
2  * Asterisk
3  * 
4  * Mark Spencer <markster@marko.net>
5  *
6  * Copyright(C) Mark Spencer
7  * 
8  * Distributed under the terms of the GNU General Public License (GPL) Version 2
9  *
10  * I/O Managment (Derived from Cheops-NG)
11  *
12  */
13
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <termios.h>
18 #include <string.h> /* for memset */
19 #include <sys/ioctl.h>
20
21 #include "asterisk.h"
22
23 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
24
25 #include "asterisk/io.h"
26 #include "asterisk/logger.h"
27
28 #ifdef DEBUG_IO
29 #define DEBUG DEBUG_M
30 #else
31 #define DEBUG(a) 
32 #endif
33
34 /* 
35  * Kept for each file descriptor
36  */
37 struct io_rec {
38         ast_io_cb callback;             /* What is to be called */
39         void *data;                             /* Data to be passed */
40         int *id;                                        /* ID number */
41 };
42
43 /* These two arrays are keyed with
44    the same index.  it's too bad that
45    pollfd doesn't have a callback field
46    or something like that.  They grow as
47    needed, by GROW_SHRINK_AMOUNT structures
48    at once */
49
50 #define GROW_SHRINK_SIZE 512
51
52 /* Global variables are now in a struct in order to be
53    made threadsafe */
54 struct io_context {
55         /* Poll structure */
56         struct pollfd *fds;
57         /* Associated I/O records */
58         struct io_rec *ior;
59         /* First available fd */
60         unsigned int fdcnt;
61         /* Maximum available fd */
62         unsigned int maxfdcnt;
63         /* Currently used io callback */
64         int current_ioc;
65         /* Whether something has been deleted */
66         int needshrink;
67 };
68
69 struct io_context *io_context_create(void)
70 {
71         /* Create an I/O context */
72         struct io_context *tmp;
73         tmp = malloc(sizeof(struct io_context));
74         if (tmp) {
75                 tmp->needshrink = 0;
76                 tmp->fdcnt = 0;
77                 tmp->maxfdcnt = GROW_SHRINK_SIZE/2;
78                 tmp->current_ioc = -1;
79                 tmp->fds = malloc((GROW_SHRINK_SIZE/2) * sizeof(struct pollfd));
80                 if (!tmp->fds) {
81                         free(tmp);
82                         tmp = NULL;
83                 } else {
84                         memset(tmp->fds, 0, (GROW_SHRINK_SIZE / 2) * sizeof(struct pollfd));
85                         tmp->ior =  malloc((GROW_SHRINK_SIZE / 2) * sizeof(struct io_rec));
86                         if (!tmp->ior) {
87                                 free(tmp->fds);
88                                 free(tmp);
89                                 tmp = NULL;
90                         } else {
91                                 memset(tmp->ior, 0, (GROW_SHRINK_SIZE / 2) * sizeof(struct io_rec));
92                         }
93                 }
94         }
95         return tmp;
96 }
97
98 void io_context_destroy(struct io_context *ioc)
99 {
100         /* Free associated memory with an I/O context */
101         if (ioc->fds)
102                 free(ioc->fds);
103         if (ioc->ior)
104                 free(ioc->ior);
105         free(ioc);
106 }
107
108 static int io_grow(struct io_context *ioc)
109 {
110         /* 
111          * Grow the size of our arrays.  Return 0 on success or
112          * -1 on failure
113          */
114         void *tmp;
115         DEBUG(ast_log(LOG_DEBUG, "io_grow()\n"));
116         ioc->maxfdcnt += GROW_SHRINK_SIZE;
117         tmp = realloc(ioc->ior, (ioc->maxfdcnt + 1) * sizeof(struct io_rec));
118         if (tmp) {
119                 ioc->ior = (struct io_rec *)tmp;
120                 tmp = realloc(ioc->fds, (ioc->maxfdcnt + 1) * sizeof(struct pollfd));
121                 if (tmp) {
122                         ioc->fds = tmp;
123                 } else {
124                         /*
125                          * Not enough memory for the pollfd.  Not really any need
126                          * to shrink back the iorec's as we'll probably want to
127                          * grow them again soon when more memory is available, and
128                          * then they'll already be the right size
129                          */
130                         ioc->maxfdcnt -= GROW_SHRINK_SIZE;
131                         return -1;
132                 }
133         } else {
134                 /*
135                  * Out of memory.  We return to the old size, and return a failure
136                  */
137                 ioc->maxfdcnt -= GROW_SHRINK_SIZE;
138                 return -1;
139         }
140         return 0;
141 }
142
143 int *ast_io_add(struct io_context *ioc, int fd, ast_io_cb callback, short events, void *data)
144 {
145         /*
146          * Add a new I/O entry for this file descriptor
147          * with the given event mask, to call callback with
148          * data as an argument.  Returns NULL on failure.
149          */
150         int *ret;
151         DEBUG(ast_log(LOG_DEBUG, "ast_io_add()\n"));
152         if (ioc->fdcnt >= ioc->maxfdcnt) {
153                 /* 
154                  * We don't have enough space for this entry.  We need to
155                  * reallocate maxfdcnt poll fd's and io_rec's, or back out now.
156                  */
157                 if (io_grow(ioc))
158                         return NULL;
159         }
160
161         /*
162          * At this point, we've got sufficiently large arrays going
163          * and we can make an entry for it in the pollfd and io_r
164          * structures.
165          */
166         ioc->fds[ioc->fdcnt].fd = fd;
167         ioc->fds[ioc->fdcnt].events = events;
168         ioc->ior[ioc->fdcnt].callback = callback;
169         ioc->ior[ioc->fdcnt].data = data;
170         ioc->ior[ioc->fdcnt].id = (int *)malloc(sizeof(int));
171         /* Bonk if we couldn't allocate an int */
172         if (!ioc->ior[ioc->fdcnt].id)
173                 return NULL;
174         *(ioc->ior[ioc->fdcnt].id) = ioc->fdcnt;
175         ret = ioc->ior[ioc->fdcnt].id;
176         ioc->fdcnt++;
177         return ret;
178 }
179
180 int *ast_io_change(struct io_context *ioc, int *id, int fd, ast_io_cb callback, short events, void *data)
181 {
182         if (*id < ioc->fdcnt) {
183                 if (fd > -1)
184                         ioc->fds[*id].fd = fd;
185                 if (callback)
186                         ioc->ior[*id].callback = callback;
187                 if (events)
188                         ioc->fds[*id].events = events;
189                 if (data)
190                         ioc->ior[*id].data = data;
191                 return id;
192         }
193         return NULL;
194 }
195
196 static int io_shrink(struct io_context *ioc)
197 {
198         int getfrom;
199         int putto = 0;
200         /* 
201          * Bring the fields from the very last entry to cover over
202          * the entry we are removing, then decrease the size of the 
203          * arrays by one.
204          */
205         for (getfrom = 0; getfrom < ioc->fdcnt; getfrom++) {
206                 if (ioc->ior[getfrom].id) {
207                         /* In use, save it */
208                         if (getfrom != putto) {
209                                 ioc->fds[putto] = ioc->fds[getfrom];
210                                 ioc->ior[putto] = ioc->ior[getfrom];
211                                 *(ioc->ior[putto].id) = putto;
212                         }
213                         putto++;
214                 }
215         }
216         ioc->fdcnt = putto;
217         ioc->needshrink = 0;
218         /* FIXME: We should free some memory if we have lots of unused
219            io structs */
220         return 0;
221 }
222
223 int ast_io_remove(struct io_context *ioc, int *_id)
224 {
225         int x;
226         if (!_id) {
227                 ast_log(LOG_WARNING, "Asked to remove NULL?\n");
228                 return -1;
229         }
230         for (x = 0; x < ioc->fdcnt; x++) {
231                 if (ioc->ior[x].id == _id) {
232                         /* Free the int immediately and set to NULL so we know it's unused now */
233                         free(ioc->ior[x].id);
234                         ioc->ior[x].id = NULL;
235                         ioc->fds[x].events = 0;
236                         ioc->fds[x].revents = 0;
237                         ioc->needshrink = 1;
238                         if (!ioc->current_ioc)
239                                 io_shrink(ioc);
240                         return 0;
241                 }
242         }
243         
244         ast_log(LOG_NOTICE, "Unable to remove unknown id %p\n", _id);
245         return -1;
246 }
247
248 int ast_io_wait(struct io_context *ioc, int howlong)
249 {
250         /*
251          * Make the poll call, and call
252          * the callbacks for anything that needs
253          * to be handled
254          */
255         int res;
256         int x;
257         int origcnt;
258         DEBUG(ast_log(LOG_DEBUG, "ast_io_wait()\n"));
259         res = poll(ioc->fds, ioc->fdcnt, howlong);
260         if (res > 0) {
261                 /*
262                  * At least one event
263                  */
264                 origcnt = ioc->fdcnt;
265                 for(x = 0; x < origcnt; x++) {
266                         /* Yes, it is possible for an entry to be deleted and still have an
267                            event waiting if it occurs after the original calling id */
268                         if (ioc->fds[x].revents && ioc->ior[x].id) {
269                                 /* There's an event waiting */
270                                 ioc->current_ioc = *ioc->ior[x].id;
271                                 if (ioc->ior[x].callback) {
272                                         if (!ioc->ior[x].callback(ioc->ior[x].id, ioc->fds[x].fd, ioc->fds[x].revents, ioc->ior[x].data)) {
273                                                 /* Time to delete them since they returned a 0 */
274                                                 ast_io_remove(ioc, ioc->ior[x].id);
275                                         }
276                                 }
277                                 ioc->current_ioc = -1;
278                         }
279                 }
280                 if (ioc->needshrink)
281                         io_shrink(ioc);
282         }
283         return res;
284 }
285
286 void ast_io_dump(struct io_context *ioc)
287 {
288         /*
289          * Print some debugging information via
290          * the logger interface
291          */
292         int x;
293         ast_log(LOG_DEBUG, "Asterisk IO Dump: %d entries, %d max entries\n", ioc->fdcnt, ioc->maxfdcnt);
294         ast_log(LOG_DEBUG, "================================================\n");
295         ast_log(LOG_DEBUG, "| ID    FD     Callback    Data        Events  |\n");
296         ast_log(LOG_DEBUG, "+------+------+-----------+-----------+--------+\n");
297         for (x = 0; x < ioc->fdcnt; x++) {
298                 ast_log(LOG_DEBUG, "| %.4d | %.4d | %p | %p | %.6x |\n", 
299                                 *ioc->ior[x].id,
300                                 ioc->fds[x].fd,
301                                 ioc->ior[x].callback,
302                                 ioc->ior[x].data,
303                                 ioc->fds[x].events);
304         }
305         ast_log(LOG_DEBUG, "================================================\n");
306 }
307
308 /* Unrelated I/O functions */
309
310 int ast_hide_password(int fd)
311 {
312         struct termios tios;
313         int res;
314         int old;
315         if (!isatty(fd))
316                 return -1;
317         res = tcgetattr(fd, &tios);
318         if (res < 0)
319                 return -1;
320         old = tios.c_lflag & (ECHO | ECHONL);
321         tios.c_lflag &= ~ECHO;
322         tios.c_lflag |= ECHONL;
323         res = tcsetattr(fd, TCSAFLUSH, &tios);
324         if (res < 0)
325                 return -1;
326         return old;
327 }
328
329 int ast_restore_tty(int fd, int oldstate)
330 {
331         int res;
332         struct termios tios;
333         if (oldstate < 0)
334                 return 0;
335         res = tcgetattr(fd, &tios);
336         if (res < 0)
337                 return -1;
338         tios.c_lflag &= ~(ECHO | ECHONL);
339         tios.c_lflag |= oldstate;
340         res = tcsetattr(fd, TCSAFLUSH, &tios);
341         if (res < 0)
342                 return -1;
343         return 0;
344 }
345
346 int ast_get_termcols(int fd)
347 {
348         struct winsize win;
349         int cols = 0;
350
351         if (!isatty(fd))
352                 return -1;
353
354         if ( ioctl(fd, TIOCGWINSZ, &win) != -1 ) {
355                 if ( !cols && win.ws_col > 0 )
356                         cols = (int) win.ws_col;
357         } else {
358                 /* assume 80 characters if the ioctl fails for some reason */
359                 cols = 80;
360         }
361
362         return cols;
363 }
364