git migration: Refactor the ASTERISK_FILE_VERSION macro
[asterisk/asterisk.git] / channels / iax2 / firmware.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.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 IAX Firmware Support
22  *
23  * \author Mark Spencer <markster@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_REGISTER_FILE()
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <dirent.h>
39 #include <sys/mman.h>
40 #include <arpa/inet.h>
41
42 #include "asterisk/linkedlists.h"
43 #include "asterisk/md5.h"
44 #include "asterisk/paths.h"
45 #include "asterisk/utils.h"
46
47 #include "include/firmware.h"
48
49 struct iax_firmware {
50         AST_LIST_ENTRY(iax_firmware) list;
51         int fd;
52         int mmaplen;
53         int dead;
54         struct ast_iax2_firmware_header *fwh;
55         unsigned char *buf;
56 };
57
58 static AST_LIST_HEAD_STATIC(firmwares, iax_firmware);
59
60 static int try_firmware(char *s)
61 {
62         struct stat stbuf;
63         struct iax_firmware *cur = NULL;
64         int ifd, fd, res, len, chunk;
65         struct ast_iax2_firmware_header *fwh, fwh2;
66         struct MD5Context md5;
67         unsigned char sum[16], buf[1024];
68         char *s2, *last;
69
70         s2 = ast_alloca(strlen(s) + 100);
71
72         last = strrchr(s, '/');
73         if (last)
74                 last++;
75         else
76                 last = s;
77
78         snprintf(s2, strlen(s) + 100, "/var/tmp/%s-%ld", last, ast_random());
79
80         if (stat(s, &stbuf) < 0) {
81                 ast_log(LOG_WARNING, "Failed to stat '%s': %s\n", s, strerror(errno));
82                 return -1;
83         }
84
85         /* Make sure it's not a directory */
86         if (S_ISDIR(stbuf.st_mode))
87                 return -1;
88         ifd = open(s, O_RDONLY);
89         if (ifd < 0) {
90                 ast_log(LOG_WARNING, "Cannot open '%s': %s\n", s, strerror(errno));
91                 return -1;
92         }
93         fd = open(s2, O_RDWR | O_CREAT | O_EXCL, AST_FILE_MODE);
94         if (fd < 0) {
95                 ast_log(LOG_WARNING, "Cannot open '%s' for writing: %s\n", s2, strerror(errno));
96                 close(ifd);
97                 return -1;
98         }
99         /* Unlink our newly created file */
100         unlink(s2);
101
102         /* Now copy the firmware into it */
103         len = stbuf.st_size;
104         while(len) {
105                 chunk = len;
106                 if (chunk > sizeof(buf))
107                         chunk = sizeof(buf);
108                 res = read(ifd, buf, chunk);
109                 if (res != chunk) {
110                         ast_log(LOG_WARNING, "Only read %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
111                         close(ifd);
112                         close(fd);
113                         return -1;
114                 }
115                 res = write(fd, buf, chunk);
116                 if (res != chunk) {
117                         ast_log(LOG_WARNING, "Only write %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
118                         close(ifd);
119                         close(fd);
120                         return -1;
121                 }
122                 len -= chunk;
123         }
124         close(ifd);
125         /* Return to the beginning */
126         lseek(fd, 0, SEEK_SET);
127         if ((res = read(fd, &fwh2, sizeof(fwh2))) != sizeof(fwh2)) {
128                 ast_log(LOG_WARNING, "Unable to read firmware header in '%s'\n", s);
129                 close(fd);
130                 return -1;
131         }
132         if (ntohl(fwh2.magic) != IAX_FIRMWARE_MAGIC) {
133                 ast_log(LOG_WARNING, "'%s' is not a valid firmware file\n", s);
134                 close(fd);
135                 return -1;
136         }
137         if (ntohl(fwh2.datalen) != (stbuf.st_size - sizeof(fwh2))) {
138                 ast_log(LOG_WARNING, "Invalid data length in firmware '%s'\n", s);
139                 close(fd);
140                 return -1;
141         }
142         if (fwh2.devname[sizeof(fwh2.devname) - 1] || ast_strlen_zero((char *)fwh2.devname)) {
143                 ast_log(LOG_WARNING, "No or invalid device type specified for '%s'\n", s);
144                 close(fd);
145                 return -1;
146         }
147         fwh = (struct ast_iax2_firmware_header*)mmap(NULL, stbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
148         if (fwh == MAP_FAILED) {
149                 ast_log(LOG_WARNING, "mmap failed: %s\n", strerror(errno));
150                 close(fd);
151                 return -1;
152         }
153         MD5Init(&md5);
154         MD5Update(&md5, fwh->data, ntohl(fwh->datalen));
155         MD5Final(sum, &md5);
156         if (memcmp(sum, fwh->chksum, sizeof(sum))) {
157                 ast_log(LOG_WARNING, "Firmware file '%s' fails checksum\n", s);
158                 munmap((void*)fwh, stbuf.st_size);
159                 close(fd);
160                 return -1;
161         }
162
163         AST_LIST_TRAVERSE(&firmwares, cur, list) {
164                 if (!strcmp((const char *) cur->fwh->devname, (const char *) fwh->devname)) {
165                         /* Found a candidate */
166                         if (cur->dead || (ntohs(cur->fwh->version) < ntohs(fwh->version)))
167                                 /* The version we have on loaded is older, load this one instead */
168                                 break;
169                         /* This version is no newer than what we have.  Don't worry about it.
170                            We'll consider it a proper load anyhow though */
171                         munmap((void*)fwh, stbuf.st_size);
172                         close(fd);
173                         return 0;
174                 }
175         }
176
177         if (!cur && ((cur = ast_calloc(1, sizeof(*cur))))) {
178                 cur->fd = -1;
179                 AST_LIST_INSERT_TAIL(&firmwares, cur, list);
180         }
181
182         if (cur) {
183                 if (cur->fwh)
184                         munmap((void*)cur->fwh, cur->mmaplen);
185                 if (cur->fd > -1)
186                         close(cur->fd);
187                 cur->fwh = fwh;
188                 cur->fd = fd;
189                 cur->mmaplen = stbuf.st_size;
190                 cur->dead = 0;
191         }
192
193         return 0;
194 }
195
196 static void destroy_firmware(struct iax_firmware *cur)
197 {
198         /* Close firmware */
199         if (cur->fwh) {
200                 munmap((void*)cur->fwh, ntohl(cur->fwh->datalen) + sizeof(*(cur->fwh)));
201         }
202         close(cur->fd);
203         ast_free(cur);
204 }
205
206 void iax_firmware_reload(void)
207 {
208         struct iax_firmware *cur = NULL;
209         DIR *fwd;
210         struct dirent *de;
211         char dir[256], fn[256];
212
213         AST_LIST_LOCK(&firmwares);
214
215         /* Mark all as dead */
216         AST_LIST_TRAVERSE(&firmwares, cur, list) {
217                 cur->dead = 1;
218         }
219
220         /* Now that we have marked them dead... load new ones */
221         snprintf(dir, sizeof(dir), "%s/firmware/iax", ast_config_AST_DATA_DIR);
222         fwd = opendir(dir);
223         if (fwd) {
224                 while((de = readdir(fwd))) {
225                         if (de->d_name[0] != '.') {
226                                 snprintf(fn, sizeof(fn), "%s/%s", dir, de->d_name);
227                                 if (!try_firmware(fn)) {
228                                         ast_verb(2, "Loaded firmware '%s'\n", de->d_name);
229                                 }
230                         }
231                 }
232                 closedir(fwd);
233         } else {
234                 ast_log(LOG_WARNING, "Error opening firmware directory '%s': %s\n", dir, strerror(errno));
235         }
236
237         /* Clean up leftovers */
238         AST_LIST_TRAVERSE_SAFE_BEGIN(&firmwares, cur, list) {
239                 if (!cur->dead)
240                         continue;
241                 AST_LIST_REMOVE_CURRENT(list);
242                 destroy_firmware(cur);
243         }
244         AST_LIST_TRAVERSE_SAFE_END;
245
246         AST_LIST_UNLOCK(&firmwares);
247 }
248
249 void iax_firmware_unload(void)
250 {
251         struct iax_firmware *cur = NULL;
252
253         AST_LIST_LOCK(&firmwares);
254         AST_LIST_TRAVERSE_SAFE_BEGIN(&firmwares, cur, list) {
255                 AST_LIST_REMOVE_CURRENT(list);
256                 destroy_firmware(cur);
257         }
258         AST_LIST_TRAVERSE_SAFE_END;
259         AST_LIST_UNLOCK(&firmwares);
260 }
261
262 int iax_firmware_get_version(const char *dev, uint16_t *version)
263 {
264         struct iax_firmware *cur = NULL;
265
266         if (ast_strlen_zero(dev))
267                 return 0;
268
269         AST_LIST_LOCK(&firmwares);
270         AST_LIST_TRAVERSE(&firmwares, cur, list) {
271                 if (!strcmp(dev, (const char *) cur->fwh->devname)) {
272                         *version = ntohs(cur->fwh->version);
273                         AST_LIST_UNLOCK(&firmwares);
274                         return 1;
275                 }
276         }
277         AST_LIST_UNLOCK(&firmwares);
278
279         return 0;
280 }
281
282 int iax_firmware_append(struct iax_ie_data *ied, const char *dev, unsigned int desc)
283 {
284         int res = -1;
285         unsigned int bs = desc & 0xff;
286         unsigned int start = (desc >> 8) & 0xffffff;
287         unsigned int bytes;
288         struct iax_firmware *cur;
289
290         if (ast_strlen_zero((char *)dev) || !bs)
291                 return -1;
292
293         start *= bs;
294
295         AST_LIST_LOCK(&firmwares);
296         AST_LIST_TRAVERSE(&firmwares, cur, list) {
297                 if (strcmp(dev, (const char *) cur->fwh->devname))
298                         continue;
299                 iax_ie_append_int(ied, IAX_IE_FWBLOCKDESC, desc);
300                 if (start < ntohl(cur->fwh->datalen)) {
301                         bytes = ntohl(cur->fwh->datalen) - start;
302                         if (bytes > bs)
303                                 bytes = bs;
304                         iax_ie_append_raw(ied, IAX_IE_FWBLOCKDATA, cur->fwh->data + start, bytes);
305                 } else {
306                         bytes = 0;
307                         iax_ie_append(ied, IAX_IE_FWBLOCKDATA);
308                 }
309                 if (bytes == bs)
310                         res = 0;
311                 else
312                         res = 1;
313                 break;
314         }
315         AST_LIST_UNLOCK(&firmwares);
316
317         return res;
318 }
319
320 void iax_firmware_traverse(
321         const char *filter,
322         int (*callback)(struct ast_iax2_firmware_header *header, void *data),
323         void *data)
324 {
325         struct iax_firmware *cur = NULL;
326
327         if (!callback) {
328                 return;
329         }
330
331         AST_LIST_LOCK(&firmwares);
332         AST_LIST_TRAVERSE(&firmwares, cur, list) {
333                 if (!filter || !strcasecmp(filter, (const char *) cur->fwh->devname)) {
334                         if (callback(cur->fwh, data)) {
335                                 break;
336                         }
337                 }
338         }
339         AST_LIST_UNLOCK(&firmwares);
340 }