1 | /* MiniDLNA project |
---|
2 | * |
---|
3 | * http://sourceforge.net/projects/minidlna/ |
---|
4 | * |
---|
5 | * MiniDLNA media server |
---|
6 | * Copyright (C) 2008-2009 Justin Maggard |
---|
7 | * |
---|
8 | * This file is part of MiniDLNA. |
---|
9 | * |
---|
10 | * MiniDLNA is free software; you can redistribute it and/or modify |
---|
11 | * it under the terms of the GNU General Public License version 2 as |
---|
12 | * published by the Free Software Foundation. |
---|
13 | * |
---|
14 | * MiniDLNA is distributed in the hope that it will be useful, |
---|
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
17 | * GNU General Public License for more details. |
---|
18 | * |
---|
19 | * You should have received a copy of the GNU General Public License |
---|
20 | * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. |
---|
21 | * |
---|
22 | * Portions of the code from the MiniUPnP project: |
---|
23 | * |
---|
24 | * Copyright (c) 2006-2007, Thomas Bernard |
---|
25 | * All rights reserved. |
---|
26 | * |
---|
27 | * Redistribution and use in source and binary forms, with or without |
---|
28 | * modification, are permitted provided that the following conditions are met: |
---|
29 | * * Redistributions of source code must retain the above copyright |
---|
30 | * notice, this list of conditions and the following disclaimer. |
---|
31 | * * Redistributions in binary form must reproduce the above copyright |
---|
32 | * notice, this list of conditions and the following disclaimer in the |
---|
33 | * documentation and/or other materials provided with the distribution. |
---|
34 | * * The name of the author may not be used to endorse or promote products |
---|
35 | * derived from this software without specific prior written permission. |
---|
36 | * |
---|
37 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
---|
38 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
---|
39 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
---|
40 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
---|
41 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
---|
42 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
---|
43 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
---|
44 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
---|
45 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
---|
46 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
---|
47 | * POSSIBILITY OF SUCH DAMAGE. |
---|
48 | */ |
---|
49 | #include <stdlib.h> |
---|
50 | #include <unistd.h> |
---|
51 | #include <string.h> |
---|
52 | #include <stdio.h> |
---|
53 | #include <ctype.h> |
---|
54 | #include <sys/types.h> |
---|
55 | #include <sys/socket.h> |
---|
56 | #include <netinet/in.h> |
---|
57 | #include <arpa/inet.h> |
---|
58 | #include <fcntl.h> |
---|
59 | #include <sys/file.h> |
---|
60 | #include <sys/time.h> |
---|
61 | #include <time.h> |
---|
62 | #include <signal.h> |
---|
63 | #include <sys/param.h> |
---|
64 | #include <errno.h> |
---|
65 | #include <pthread.h> |
---|
66 | #include <pwd.h> |
---|
67 | |
---|
68 | #include "config.h" |
---|
69 | |
---|
70 | #ifdef ENABLE_NLS |
---|
71 | #include <libintl.h> |
---|
72 | #endif |
---|
73 | |
---|
74 | #include "upnpglobalvars.h" |
---|
75 | #include "sql.h" |
---|
76 | #include "upnphttp.h" |
---|
77 | #include "upnpdescgen.h" |
---|
78 | #include "minidlnapath.h" |
---|
79 | #include "getifaddr.h" |
---|
80 | #include "upnpsoap.h" |
---|
81 | #include "options.h" |
---|
82 | #include "utils.h" |
---|
83 | #include "minissdp.h" |
---|
84 | #include "minidlnatypes.h" |
---|
85 | #include "daemonize.h" |
---|
86 | #include "upnpevents.h" |
---|
87 | #include "scanner.h" |
---|
88 | #include "inotify.h" |
---|
89 | #include "log.h" |
---|
90 | #ifdef TIVO_SUPPORT |
---|
91 | #include "tivo_beacon.h" |
---|
92 | #include "tivo_utils.h" |
---|
93 | #endif |
---|
94 | |
---|
95 | #if SQLITE_VERSION_NUMBER < 3005001 |
---|
96 | # warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer." |
---|
97 | # define sqlite3_threadsafe() 0 |
---|
98 | #endif |
---|
99 | |
---|
100 | /* OpenAndConfHTTPSocket() : |
---|
101 | * setup the socket used to handle incoming HTTP connections. */ |
---|
102 | static int |
---|
103 | OpenAndConfHTTPSocket(unsigned short port) |
---|
104 | { |
---|
105 | int s; |
---|
106 | int i = 1; |
---|
107 | struct sockaddr_in listenname; |
---|
108 | |
---|
109 | /* Initialize client type cache */ |
---|
110 | memset(&clients, 0, sizeof(struct client_cache_s)); |
---|
111 | |
---|
112 | if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0) |
---|
113 | { |
---|
114 | DPRINTF(E_ERROR, L_GENERAL, "socket(http): %s\n", strerror(errno)); |
---|
115 | return -1; |
---|
116 | } |
---|
117 | |
---|
118 | if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) |
---|
119 | { |
---|
120 | DPRINTF(E_WARN, L_GENERAL, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); |
---|
121 | } |
---|
122 | |
---|
123 | memset(&listenname, 0, sizeof(struct sockaddr_in)); |
---|
124 | listenname.sin_family = AF_INET; |
---|
125 | listenname.sin_port = htons(port); |
---|
126 | listenname.sin_addr.s_addr = htonl(INADDR_ANY); |
---|
127 | |
---|
128 | if(bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0) |
---|
129 | { |
---|
130 | DPRINTF(E_ERROR, L_GENERAL, "bind(http): %s\n", strerror(errno)); |
---|
131 | close(s); |
---|
132 | return -1; |
---|
133 | } |
---|
134 | |
---|
135 | if(listen(s, 6) < 0) |
---|
136 | { |
---|
137 | DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); |
---|
138 | close(s); |
---|
139 | return -1; |
---|
140 | } |
---|
141 | |
---|
142 | return s; |
---|
143 | } |
---|
144 | |
---|
145 | /* Handler for the SIGTERM signal (kill) |
---|
146 | * SIGINT is also handled */ |
---|
147 | static void |
---|
148 | sigterm(int sig) |
---|
149 | { |
---|
150 | /*int save_errno = errno;*/ |
---|
151 | signal(sig, SIG_IGN); /* Ignore this signal while we are quitting */ |
---|
152 | |
---|
153 | DPRINTF(E_WARN, L_GENERAL, "received signal %d, good-bye\n", sig); |
---|
154 | |
---|
155 | quitting = 1; |
---|
156 | /*errno = save_errno;*/ |
---|
157 | } |
---|
158 | |
---|
159 | /* record the startup time, for returning uptime */ |
---|
160 | static void |
---|
161 | set_startup_time(void) |
---|
162 | { |
---|
163 | startup_time = time(NULL); |
---|
164 | } |
---|
165 | |
---|
166 | /* parselanaddr() |
---|
167 | * parse address with mask |
---|
168 | * ex: 192.168.1.1/24 |
---|
169 | * return value : |
---|
170 | * 0 : ok |
---|
171 | * -1 : error */ |
---|
172 | static int |
---|
173 | parselanaddr(struct lan_addr_s * lan_addr, const char * str) |
---|
174 | { |
---|
175 | const char * p; |
---|
176 | int nbits = 24; |
---|
177 | int n; |
---|
178 | p = str; |
---|
179 | while(*p && *p != '/' && !isspace(*p)) |
---|
180 | p++; |
---|
181 | n = p - str; |
---|
182 | if(*p == '/') |
---|
183 | { |
---|
184 | nbits = atoi(++p); |
---|
185 | while(*p && !isspace(*p)) |
---|
186 | p++; |
---|
187 | } |
---|
188 | if(n>15) |
---|
189 | { |
---|
190 | DPRINTF(E_OFF, L_GENERAL, "Error parsing address/mask: %s\n", str); |
---|
191 | return -1; |
---|
192 | } |
---|
193 | memcpy(lan_addr->str, str, n); |
---|
194 | lan_addr->str[n] = '\0'; |
---|
195 | if(!inet_aton(lan_addr->str, &lan_addr->addr)) |
---|
196 | { |
---|
197 | DPRINTF(E_OFF, L_GENERAL, "Error parsing address: %s\n", str); |
---|
198 | return -1; |
---|
199 | } |
---|
200 | lan_addr->mask.s_addr = htonl(nbits ? (0xffffffff << (32 - nbits)) : 0); |
---|
201 | return 0; |
---|
202 | } |
---|
203 | |
---|
204 | static void |
---|
205 | getfriendlyname(char * buf, int len) |
---|
206 | { |
---|
207 | char * dot = NULL; |
---|
208 | char * hn = calloc(1, 256); |
---|
209 | int off; |
---|
210 | |
---|
211 | if( gethostname(hn, 256) == 0 ) |
---|
212 | { |
---|
213 | strncpy(buf, hn, len-1); |
---|
214 | buf[len] = '\0'; |
---|
215 | dot = strchr(buf, '.'); |
---|
216 | if( dot ) |
---|
217 | *dot = '\0'; |
---|
218 | } |
---|
219 | else |
---|
220 | { |
---|
221 | strcpy(buf, "Unknown"); |
---|
222 | } |
---|
223 | free(hn); |
---|
224 | |
---|
225 | off = strlen(buf); |
---|
226 | off += snprintf(buf+off, len-off, ": "); |
---|
227 | #ifdef READYNAS |
---|
228 | FILE * info; |
---|
229 | char ibuf[64], *key, *val; |
---|
230 | snprintf(buf+off, len-off, "ReadyNAS"); |
---|
231 | info = fopen("/proc/sys/dev/boot/info", "r"); |
---|
232 | if( !info ) |
---|
233 | return; |
---|
234 | while( (val = fgets(ibuf, 64, info)) != NULL ) |
---|
235 | { |
---|
236 | key = strsep(&val, ": \t"); |
---|
237 | val = trim(val); |
---|
238 | if( strcmp(key, "model") == 0 ) |
---|
239 | { |
---|
240 | snprintf(buf+off, len-off, "%s", val); |
---|
241 | key = strchr(val, ' '); |
---|
242 | if( key ) |
---|
243 | { |
---|
244 | strncpy(modelnumber, key+1, MODELNUMBER_MAX_LEN); |
---|
245 | modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; |
---|
246 | *key = '\0'; |
---|
247 | } |
---|
248 | snprintf(modelname, MODELNAME_MAX_LEN, |
---|
249 | "Windows Media Connect compatible (%s)", val); |
---|
250 | } |
---|
251 | else if( strcmp(key, "serial") == 0 ) |
---|
252 | { |
---|
253 | strncpy(serialnumber, val, SERIALNUMBER_MAX_LEN); |
---|
254 | serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; |
---|
255 | if( serialnumber[0] == '\0' ) |
---|
256 | { |
---|
257 | char mac_str[13]; |
---|
258 | if( getsyshwaddr(mac_str, sizeof(mac_str)) == 0 ) |
---|
259 | strcpy(serialnumber, mac_str); |
---|
260 | else |
---|
261 | strcpy(serialnumber, "0"); |
---|
262 | } |
---|
263 | break; |
---|
264 | } |
---|
265 | } |
---|
266 | fclose(info); |
---|
267 | memcpy(pnpx_hwid+4, "01F2", 4); |
---|
268 | if( strcmp(modelnumber, "NVX") == 0 ) |
---|
269 | memcpy(pnpx_hwid+17, "0101", 4); |
---|
270 | else if( strcmp(modelnumber, "Pro") == 0 || |
---|
271 | strcmp(modelnumber, "Pro 6") == 0 || |
---|
272 | strncmp(modelnumber, "Ultra 6", 7) == 0 ) |
---|
273 | memcpy(pnpx_hwid+17, "0102", 4); |
---|
274 | else if( strcmp(modelnumber, "Pro 2") == 0 || |
---|
275 | strncmp(modelnumber, "Ultra 2", 7) == 0 ) |
---|
276 | memcpy(pnpx_hwid+17, "0103", 4); |
---|
277 | else if( strcmp(modelnumber, "Pro 4") == 0 || |
---|
278 | strncmp(modelnumber, "Ultra 4", 7) == 0 ) |
---|
279 | memcpy(pnpx_hwid+17, "0104", 4); |
---|
280 | else if( strcmp(modelnumber+1, "100") == 0 ) |
---|
281 | memcpy(pnpx_hwid+17, "0105", 4); |
---|
282 | else if( strcmp(modelnumber+1, "200") == 0 ) |
---|
283 | memcpy(pnpx_hwid+17, "0106", 4); |
---|
284 | /* 0107 = Stora */ |
---|
285 | else if( strcmp(modelnumber, "Duo v2") == 0 ) |
---|
286 | memcpy(pnpx_hwid+17, "0108", 4); |
---|
287 | else if( strcmp(modelnumber, "NV+ v2") == 0 ) |
---|
288 | memcpy(pnpx_hwid+17, "0109", 4); |
---|
289 | #else |
---|
290 | char * logname; |
---|
291 | logname = getenv("LOGNAME"); |
---|
292 | #ifndef STATIC // Disable for static linking |
---|
293 | if( !logname ) |
---|
294 | { |
---|
295 | struct passwd * pwent; |
---|
296 | pwent = getpwuid(getuid()); |
---|
297 | if( pwent ) |
---|
298 | logname = pwent->pw_name; |
---|
299 | } |
---|
300 | #endif |
---|
301 | snprintf(buf+off, len-off, "%s", logname?logname:"Unknown"); |
---|
302 | #endif |
---|
303 | } |
---|
304 | |
---|
305 | static int |
---|
306 | open_db(void) |
---|
307 | { |
---|
308 | char path[PATH_MAX]; |
---|
309 | int new_db = 0; |
---|
310 | |
---|
311 | snprintf(path, sizeof(path), "%s/files.db", db_path); |
---|
312 | if( access(path, F_OK) != 0 ) |
---|
313 | { |
---|
314 | new_db = 1; |
---|
315 | make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); |
---|
316 | } |
---|
317 | if( sqlite3_open(path, &db) != SQLITE_OK ) |
---|
318 | { |
---|
319 | DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to open sqlite database! Exiting...\n"); |
---|
320 | } |
---|
321 | sqlite3_busy_timeout(db, 5000); |
---|
322 | sql_exec(db, "pragma page_size = 4096"); |
---|
323 | sql_exec(db, "pragma journal_mode = OFF"); |
---|
324 | sql_exec(db, "pragma synchronous = OFF;"); |
---|
325 | sql_exec(db, "pragma default_cache_size = 8192;"); |
---|
326 | return new_db; |
---|
327 | } |
---|
328 | |
---|
329 | /* init phase : |
---|
330 | * 1) read configuration file |
---|
331 | * 2) read command line arguments |
---|
332 | * 3) daemonize |
---|
333 | * 4) check and write pid file |
---|
334 | * 5) set startup time stamp |
---|
335 | * 6) compute presentation URL |
---|
336 | * 7) set signal handlers */ |
---|
337 | static int |
---|
338 | init(int argc, char * * argv) |
---|
339 | { |
---|
340 | int i; |
---|
341 | int pid; |
---|
342 | int debug_flag = 0; |
---|
343 | int options_flag = 0; |
---|
344 | struct sigaction sa; |
---|
345 | /*const char * logfilename = 0;*/ |
---|
346 | const char * presurl = 0; |
---|
347 | const char * optionsfile = "/etc/minidlna.conf"; |
---|
348 | char mac_str[13]; |
---|
349 | char * string, * word; |
---|
350 | enum media_types type; |
---|
351 | char * path; |
---|
352 | char real_path[PATH_MAX]; |
---|
353 | char ip_addr[INET_ADDRSTRLEN + 3] = {'\0'}; |
---|
354 | |
---|
355 | /* first check if "-f" option is used */ |
---|
356 | for(i=2; i<argc; i++) |
---|
357 | { |
---|
358 | if(0 == strcmp(argv[i-1], "-f")) |
---|
359 | { |
---|
360 | optionsfile = argv[i]; |
---|
361 | options_flag = 1; |
---|
362 | break; |
---|
363 | } |
---|
364 | } |
---|
365 | |
---|
366 | /* set up uuid based on mac address */ |
---|
367 | if( getsyshwaddr(mac_str, sizeof(mac_str)) < 0 ) |
---|
368 | { |
---|
369 | DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n"); |
---|
370 | strcpy(mac_str, "554e4b4e4f57"); |
---|
371 | } |
---|
372 | strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-"); |
---|
373 | strncat(uuidvalue, mac_str, 12); |
---|
374 | |
---|
375 | getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); |
---|
376 | |
---|
377 | runtime_vars.port = -1; |
---|
378 | runtime_vars.notify_interval = 895; /* seconds between SSDP announces */ |
---|
379 | runtime_vars.root_container = NULL; |
---|
380 | |
---|
381 | /* read options file first since |
---|
382 | * command line arguments have final say */ |
---|
383 | if(readoptionsfile(optionsfile) < 0) |
---|
384 | { |
---|
385 | /* only error if file exists or using -f */ |
---|
386 | if(access(optionsfile, F_OK) == 0 || options_flag) |
---|
387 | fprintf(stderr, "Error reading configuration file %s\n", optionsfile); |
---|
388 | } |
---|
389 | else |
---|
390 | { |
---|
391 | for(i=0; i<num_options; i++) |
---|
392 | { |
---|
393 | switch(ary_options[i].id) |
---|
394 | { |
---|
395 | case UPNPIFNAME: |
---|
396 | for( string = ary_options[i].value; (word = strtok(string, ",")); string = NULL ) |
---|
397 | { |
---|
398 | if(n_lan_addr < MAX_LAN_ADDR) |
---|
399 | { |
---|
400 | if(getifaddr(word, ip_addr, sizeof(ip_addr)) >= 0) |
---|
401 | { |
---|
402 | if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) |
---|
403 | if(n_lan_addr < MAX_LAN_ADDR) |
---|
404 | n_lan_addr++; |
---|
405 | } |
---|
406 | else |
---|
407 | fprintf(stderr, "Interface %s not found, ignoring.\n", word); |
---|
408 | } |
---|
409 | else |
---|
410 | { |
---|
411 | fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", |
---|
412 | MAX_LAN_ADDR, word); |
---|
413 | } |
---|
414 | } |
---|
415 | break; |
---|
416 | case UPNPLISTENING_IP: |
---|
417 | if(n_lan_addr < MAX_LAN_ADDR) |
---|
418 | { |
---|
419 | if(parselanaddr(&lan_addr[n_lan_addr], |
---|
420 | ary_options[i].value) == 0) |
---|
421 | n_lan_addr++; |
---|
422 | } |
---|
423 | else |
---|
424 | { |
---|
425 | fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", |
---|
426 | MAX_LAN_ADDR, ary_options[i].value); |
---|
427 | } |
---|
428 | break; |
---|
429 | case UPNPPORT: |
---|
430 | runtime_vars.port = atoi(ary_options[i].value); |
---|
431 | break; |
---|
432 | case UPNPPRESENTATIONURL: |
---|
433 | presurl = ary_options[i].value; |
---|
434 | break; |
---|
435 | case UPNPNOTIFY_INTERVAL: |
---|
436 | runtime_vars.notify_interval = atoi(ary_options[i].value); |
---|
437 | break; |
---|
438 | case UPNPSERIAL: |
---|
439 | strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); |
---|
440 | serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; |
---|
441 | break; |
---|
442 | case UPNPMODEL_NAME: |
---|
443 | strncpy(modelname, ary_options[i].value, MODELNAME_MAX_LEN); |
---|
444 | modelname[MODELNAME_MAX_LEN-1] = '\0'; |
---|
445 | break; |
---|
446 | case UPNPMODEL_NUMBER: |
---|
447 | strncpy(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); |
---|
448 | modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; |
---|
449 | break; |
---|
450 | case UPNPFRIENDLYNAME: |
---|
451 | strncpy(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); |
---|
452 | friendly_name[FRIENDLYNAME_MAX_LEN-1] = '\0'; |
---|
453 | break; |
---|
454 | case UPNPMEDIADIR: |
---|
455 | type = ALL_MEDIA; |
---|
456 | char * myval = NULL; |
---|
457 | switch( ary_options[i].value[0] ) |
---|
458 | { |
---|
459 | case 'A': |
---|
460 | case 'a': |
---|
461 | if( ary_options[i].value[0] == 'A' || ary_options[i].value[0] == 'a' ) |
---|
462 | type = AUDIO_ONLY; |
---|
463 | case 'V': |
---|
464 | case 'v': |
---|
465 | if( ary_options[i].value[0] == 'V' || ary_options[i].value[0] == 'v' ) |
---|
466 | type = VIDEO_ONLY; |
---|
467 | case 'P': |
---|
468 | case 'p': |
---|
469 | if( ary_options[i].value[0] == 'P' || ary_options[i].value[0] == 'p' ) |
---|
470 | type = IMAGES_ONLY; |
---|
471 | myval = index(ary_options[i].value, '/'); |
---|
472 | case '/': |
---|
473 | path = realpath(myval ? myval:ary_options[i].value, real_path); |
---|
474 | if( !path ) |
---|
475 | path = (myval ? myval:ary_options[i].value); |
---|
476 | if( access(path, F_OK) != 0 ) |
---|
477 | { |
---|
478 | fprintf(stderr, "Media directory not accessible! [%s]\n", |
---|
479 | path); |
---|
480 | break; |
---|
481 | } |
---|
482 | struct media_dir_s * this_dir = calloc(1, sizeof(struct media_dir_s)); |
---|
483 | this_dir->path = strdup(path); |
---|
484 | this_dir->type = type; |
---|
485 | if( !media_dirs ) |
---|
486 | { |
---|
487 | media_dirs = this_dir; |
---|
488 | } |
---|
489 | else |
---|
490 | { |
---|
491 | struct media_dir_s * all_dirs = media_dirs; |
---|
492 | while( all_dirs->next ) |
---|
493 | all_dirs = all_dirs->next; |
---|
494 | all_dirs->next = this_dir; |
---|
495 | } |
---|
496 | break; |
---|
497 | default: |
---|
498 | fprintf(stderr, "Media directory entry not understood! [%s]\n", |
---|
499 | ary_options[i].value); |
---|
500 | break; |
---|
501 | } |
---|
502 | break; |
---|
503 | case UPNPALBUMART_NAMES: |
---|
504 | for( string = ary_options[i].value; (word = strtok(string, "/")); string = NULL ) |
---|
505 | { |
---|
506 | struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s)); |
---|
507 | int len = strlen(word); |
---|
508 | if( word[len-1] == '*' ) |
---|
509 | { |
---|
510 | word[len-1] = '\0'; |
---|
511 | this_name->wildcard = 1; |
---|
512 | } |
---|
513 | this_name->name = strdup(word); |
---|
514 | if( !album_art_names ) |
---|
515 | { |
---|
516 | album_art_names = this_name; |
---|
517 | } |
---|
518 | else |
---|
519 | { |
---|
520 | struct album_art_name_s * all_names = album_art_names; |
---|
521 | while( all_names->next ) |
---|
522 | all_names = all_names->next; |
---|
523 | all_names->next = this_name; |
---|
524 | } |
---|
525 | } |
---|
526 | break; |
---|
527 | case UPNPDBDIR: |
---|
528 | path = realpath(ary_options[i].value, real_path); |
---|
529 | if( !path ) |
---|
530 | path = (ary_options[i].value); |
---|
531 | make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); |
---|
532 | if( access(path, F_OK) != 0 ) |
---|
533 | { |
---|
534 | DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path); |
---|
535 | break; |
---|
536 | } |
---|
537 | strncpy(db_path, path, PATH_MAX); |
---|
538 | break; |
---|
539 | case UPNPLOGDIR: |
---|
540 | path = realpath(ary_options[i].value, real_path); |
---|
541 | if( !path ) |
---|
542 | path = (ary_options[i].value); |
---|
543 | make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); |
---|
544 | if( access(path, F_OK) != 0 ) |
---|
545 | { |
---|
546 | DPRINTF(E_FATAL, L_GENERAL, "Log path not accessible! [%s]\n", path); |
---|
547 | break; |
---|
548 | } |
---|
549 | strncpy(log_path, path, PATH_MAX); |
---|
550 | break; |
---|
551 | case UPNPINOTIFY: |
---|
552 | if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) ) |
---|
553 | CLEARFLAG(INOTIFY_MASK); |
---|
554 | break; |
---|
555 | case ENABLE_TIVO: |
---|
556 | if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) |
---|
557 | SETFLAG(TIVO_MASK); |
---|
558 | break; |
---|
559 | case ENABLE_DLNA_STRICT: |
---|
560 | if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) |
---|
561 | SETFLAG(DLNA_STRICT_MASK); |
---|
562 | break; |
---|
563 | case ROOT_CONTAINER: |
---|
564 | switch( ary_options[i].value[0] ) |
---|
565 | { |
---|
566 | case '.': |
---|
567 | runtime_vars.root_container = NULL; |
---|
568 | break; |
---|
569 | case 'B': |
---|
570 | case 'b': |
---|
571 | runtime_vars.root_container = BROWSEDIR_ID; |
---|
572 | break; |
---|
573 | case 'M': |
---|
574 | case 'm': |
---|
575 | runtime_vars.root_container = MUSIC_ID; |
---|
576 | break; |
---|
577 | case 'V': |
---|
578 | case 'v': |
---|
579 | runtime_vars.root_container = VIDEO_ID; |
---|
580 | break; |
---|
581 | case 'P': |
---|
582 | case 'p': |
---|
583 | runtime_vars.root_container = IMAGE_ID; |
---|
584 | break; |
---|
585 | default: |
---|
586 | fprintf(stderr, "Invalid root container! [%s]\n", |
---|
587 | ary_options[i].value); |
---|
588 | break; |
---|
589 | } |
---|
590 | break; |
---|
591 | case UPNPMINISSDPDSOCKET: |
---|
592 | minissdpdsocketpath = ary_options[i].value; |
---|
593 | break; |
---|
594 | default: |
---|
595 | fprintf(stderr, "Unknown option in file %s\n", |
---|
596 | optionsfile); |
---|
597 | } |
---|
598 | } |
---|
599 | } |
---|
600 | if( log_path[0] == '\0' ) |
---|
601 | { |
---|
602 | if( db_path[0] == '\0' ) |
---|
603 | strncpy(log_path, DEFAULT_LOG_PATH, PATH_MAX); |
---|
604 | else |
---|
605 | strncpy(log_path, db_path, PATH_MAX); |
---|
606 | } |
---|
607 | if( db_path[0] == '\0' ) |
---|
608 | strncpy(db_path, DEFAULT_DB_PATH, PATH_MAX); |
---|
609 | |
---|
610 | /* command line arguments processing */ |
---|
611 | for(i=1; i<argc; i++) |
---|
612 | { |
---|
613 | if(argv[i][0]!='-') |
---|
614 | { |
---|
615 | fprintf(stderr, "Unknown option: %s\n", argv[i]); |
---|
616 | } |
---|
617 | else if(strcmp(argv[i], "--help")==0) |
---|
618 | { |
---|
619 | runtime_vars.port = 0; |
---|
620 | break; |
---|
621 | } |
---|
622 | else switch(argv[i][1]) |
---|
623 | { |
---|
624 | case 't': |
---|
625 | if(i+1 < argc) |
---|
626 | runtime_vars.notify_interval = atoi(argv[++i]); |
---|
627 | else |
---|
628 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
629 | break; |
---|
630 | case 's': |
---|
631 | if(i+1 < argc) |
---|
632 | strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); |
---|
633 | else |
---|
634 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
635 | serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0'; |
---|
636 | break; |
---|
637 | case 'm': |
---|
638 | if(i+1 < argc) |
---|
639 | strncpy(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); |
---|
640 | else |
---|
641 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
642 | modelnumber[MODELNUMBER_MAX_LEN-1] = '\0'; |
---|
643 | break; |
---|
644 | /*case 'l': |
---|
645 | logfilename = argv[++i]; |
---|
646 | break;*/ |
---|
647 | case 'p': |
---|
648 | if(i+1 < argc) |
---|
649 | runtime_vars.port = atoi(argv[++i]); |
---|
650 | else |
---|
651 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
652 | break; |
---|
653 | case 'P': |
---|
654 | if(i+1 < argc) |
---|
655 | pidfilename = argv[++i]; |
---|
656 | else |
---|
657 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
658 | break; |
---|
659 | case 'd': |
---|
660 | debug_flag = 1; |
---|
661 | break; |
---|
662 | case 'w': |
---|
663 | if(i+1 < argc) |
---|
664 | presurl = argv[++i]; |
---|
665 | else |
---|
666 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
667 | break; |
---|
668 | case 'a': |
---|
669 | if(i+1 < argc) |
---|
670 | { |
---|
671 | int address_already_there = 0; |
---|
672 | int j; |
---|
673 | i++; |
---|
674 | for(j=0; j<n_lan_addr; j++) |
---|
675 | { |
---|
676 | struct lan_addr_s tmpaddr; |
---|
677 | parselanaddr(&tmpaddr, argv[i]); |
---|
678 | if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) |
---|
679 | address_already_there = 1; |
---|
680 | } |
---|
681 | if(address_already_there) |
---|
682 | break; |
---|
683 | if(n_lan_addr < MAX_LAN_ADDR) |
---|
684 | { |
---|
685 | if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) |
---|
686 | n_lan_addr++; |
---|
687 | } |
---|
688 | else |
---|
689 | { |
---|
690 | fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", |
---|
691 | MAX_LAN_ADDR, argv[i]); |
---|
692 | } |
---|
693 | } |
---|
694 | else |
---|
695 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
696 | break; |
---|
697 | case 'i': |
---|
698 | if(i+1 < argc) |
---|
699 | { |
---|
700 | int address_already_there = 0; |
---|
701 | int j; |
---|
702 | i++; |
---|
703 | if( getifaddr(argv[i], ip_addr, sizeof(ip_addr)) < 0 ) |
---|
704 | { |
---|
705 | fprintf(stderr, "Network interface '%s' not found.\n", |
---|
706 | argv[i]); |
---|
707 | exit(-1); |
---|
708 | } |
---|
709 | for(j=0; j<n_lan_addr; j++) |
---|
710 | { |
---|
711 | struct lan_addr_s tmpaddr; |
---|
712 | parselanaddr(&tmpaddr, ip_addr); |
---|
713 | if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) |
---|
714 | address_already_there = 1; |
---|
715 | } |
---|
716 | if(address_already_there) |
---|
717 | break; |
---|
718 | if(n_lan_addr < MAX_LAN_ADDR) |
---|
719 | { |
---|
720 | if(parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0) |
---|
721 | n_lan_addr++; |
---|
722 | } |
---|
723 | else |
---|
724 | { |
---|
725 | fprintf(stderr, "Too many listening ips (max: %d), ignoring %s\n", |
---|
726 | MAX_LAN_ADDR, argv[i]); |
---|
727 | } |
---|
728 | } |
---|
729 | else |
---|
730 | fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); |
---|
731 | break; |
---|
732 | case 'f': |
---|
733 | i++; /* discarding, the config file is already read */ |
---|
734 | break; |
---|
735 | case 'h': |
---|
736 | runtime_vars.port = 0; // triggers help display |
---|
737 | break; |
---|
738 | case 'R': |
---|
739 | snprintf(real_path, sizeof(real_path), "rm -rf %s/files.db %s/art_cache", db_path, db_path); |
---|
740 | system(real_path); |
---|
741 | break; |
---|
742 | case 'V': |
---|
743 | printf("Version " MINIDLNA_VERSION "\n"); |
---|
744 | exit(0); |
---|
745 | break; |
---|
746 | default: |
---|
747 | fprintf(stderr, "Unknown option: %s\n", argv[i]); |
---|
748 | } |
---|
749 | } |
---|
750 | /* If no IP was specified, try to detect one */ |
---|
751 | if( n_lan_addr < 1 ) |
---|
752 | { |
---|
753 | if( (getsysaddr(ip_addr, sizeof(ip_addr)) < 0) && |
---|
754 | (getifaddr("eth0", ip_addr, sizeof(ip_addr)) < 0) && |
---|
755 | (getifaddr("eth1", ip_addr, sizeof(ip_addr)) < 0) ) |
---|
756 | { |
---|
757 | DPRINTF(E_OFF, L_GENERAL, "No IP address automatically detected!\n"); |
---|
758 | } |
---|
759 | if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) |
---|
760 | { |
---|
761 | n_lan_addr++; |
---|
762 | } |
---|
763 | } |
---|
764 | |
---|
765 | if( (n_lan_addr==0) || (runtime_vars.port<=0) ) |
---|
766 | { |
---|
767 | fprintf(stderr, "Usage:\n\t" |
---|
768 | "%s [-d] [-f config_file]\n" |
---|
769 | "\t\t[-a listening_ip] [-p port]\n" |
---|
770 | /*"[-l logfile] " not functionnal */ |
---|
771 | "\t\t[-s serial] [-m model_number] \n" |
---|
772 | "\t\t[-t notify_interval] [-P pid_filename]\n" |
---|
773 | "\t\t[-w url] [-R] [-V] [-h]\n" |
---|
774 | "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" |
---|
775 | "\tDefault pid file is %s.\n" |
---|
776 | "\tWith -d minidlna will run in debug mode (not daemonize).\n" |
---|
777 | "\t-w sets the presentation url. Default is http address on port 80\n" |
---|
778 | "\t-h displays this text\n" |
---|
779 | "\t-R forces a full rescan\n" |
---|
780 | "\t-V print the version number\n", |
---|
781 | argv[0], pidfilename); |
---|
782 | return 1; |
---|
783 | } |
---|
784 | |
---|
785 | if(debug_flag) |
---|
786 | { |
---|
787 | pid = getpid(); |
---|
788 | log_init(NULL, "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=debug"); |
---|
789 | } |
---|
790 | else |
---|
791 | { |
---|
792 | #ifdef USE_DAEMON |
---|
793 | if(daemon(0, 0)<0) { |
---|
794 | perror("daemon()"); |
---|
795 | } |
---|
796 | pid = getpid(); |
---|
797 | #else |
---|
798 | pid = daemonize(); |
---|
799 | #endif |
---|
800 | #ifdef READYNAS |
---|
801 | log_init("/var/log/upnp-av.log", "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"); |
---|
802 | #else |
---|
803 | if( access(db_path, F_OK) != 0 ) |
---|
804 | make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); |
---|
805 | sprintf(real_path, "%s/minidlna.log", log_path); |
---|
806 | log_init(real_path, "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"); |
---|
807 | #endif |
---|
808 | } |
---|
809 | |
---|
810 | if(checkforrunning(pidfilename) < 0) |
---|
811 | { |
---|
812 | DPRINTF(E_ERROR, L_GENERAL, "MiniDLNA is already running. EXITING.\n"); |
---|
813 | return 1; |
---|
814 | } |
---|
815 | |
---|
816 | set_startup_time(); |
---|
817 | |
---|
818 | /* presentation url */ |
---|
819 | if(presurl) |
---|
820 | { |
---|
821 | strncpy(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); |
---|
822 | presentationurl[PRESENTATIONURL_MAX_LEN-1] = '\0'; |
---|
823 | } |
---|
824 | else |
---|
825 | { |
---|
826 | #ifdef READYNAS |
---|
827 | snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, |
---|
828 | "http://%s/admin/", lan_addr[0].str); |
---|
829 | #else |
---|
830 | snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, |
---|
831 | "http://%s:%d/", lan_addr[0].str, runtime_vars.port); |
---|
832 | #endif |
---|
833 | } |
---|
834 | |
---|
835 | /* set signal handler */ |
---|
836 | signal(SIGCLD, SIG_IGN); |
---|
837 | memset(&sa, 0, sizeof(struct sigaction)); |
---|
838 | sa.sa_handler = sigterm; |
---|
839 | if (sigaction(SIGTERM, &sa, NULL)) |
---|
840 | { |
---|
841 | DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGTERM handler. EXITING.\n"); |
---|
842 | } |
---|
843 | if (sigaction(SIGINT, &sa, NULL)) |
---|
844 | { |
---|
845 | DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGINT handler. EXITING.\n"); |
---|
846 | } |
---|
847 | |
---|
848 | if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) { |
---|
849 | DPRINTF(E_FATAL, L_GENERAL, "Failed to ignore SIGPIPE signals. EXITING.\n"); |
---|
850 | } |
---|
851 | |
---|
852 | writepidfile(pidfilename, pid); |
---|
853 | |
---|
854 | return 0; |
---|
855 | } |
---|
856 | |
---|
857 | /* === main === */ |
---|
858 | /* process HTTP or SSDP requests */ |
---|
859 | int |
---|
860 | main(int argc, char * * argv) |
---|
861 | { |
---|
862 | int i; |
---|
863 | int sudp = -1, shttpl = -1; |
---|
864 | int snotify[MAX_LAN_ADDR]; |
---|
865 | LIST_HEAD(httplisthead, upnphttp) upnphttphead; |
---|
866 | struct upnphttp * e = 0; |
---|
867 | struct upnphttp * next; |
---|
868 | fd_set readset; /* for select() */ |
---|
869 | fd_set writeset; |
---|
870 | struct timeval timeout, timeofday, lastnotifytime = {0, 0}; |
---|
871 | time_t lastupdatetime = 0; |
---|
872 | int max_fd = -1; |
---|
873 | int last_changecnt = 0; |
---|
874 | pid_t scanner_pid = 0; |
---|
875 | pthread_t inotify_thread = 0; |
---|
876 | struct media_dir_s *media_path, *last_path; |
---|
877 | struct album_art_name_s *art_names, *last_name; |
---|
878 | #ifdef TIVO_SUPPORT |
---|
879 | unsigned short int beacon_interval = 5; |
---|
880 | int sbeacon = -1; |
---|
881 | struct sockaddr_in tivo_bcast; |
---|
882 | struct timeval lastbeacontime = {0, 0}; |
---|
883 | #endif |
---|
884 | |
---|
885 | #ifdef ENABLE_NLS |
---|
886 | setlocale(LC_MESSAGES, ""); |
---|
887 | setlocale(LC_CTYPE, "en_US.utf8"); |
---|
888 | DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); |
---|
889 | textdomain("minidlna"); |
---|
890 | #endif |
---|
891 | |
---|
892 | if(init(argc, argv) != 0) |
---|
893 | return 1; |
---|
894 | |
---|
895 | #ifdef READYNAS |
---|
896 | DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION ".\n"); |
---|
897 | unlink("/ramfs/.upnp-av_scan"); |
---|
898 | #else |
---|
899 | DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION " [SQLite %s].\n", sqlite3_libversion()); |
---|
900 | if( !sqlite3_threadsafe() ) |
---|
901 | { |
---|
902 | DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe! " |
---|
903 | "Scanning must be finished before file serving can begin, " |
---|
904 | "and inotify will be disabled.\n"); |
---|
905 | } |
---|
906 | if( sqlite3_libversion_number() < 3005001 ) |
---|
907 | { |
---|
908 | DPRINTF(E_WARN, L_GENERAL, "SQLite library is old. Please use version 3.5.1 or newer.\n"); |
---|
909 | } |
---|
910 | #endif |
---|
911 | LIST_INIT(&upnphttphead); |
---|
912 | |
---|
913 | if( open_db() == 0 ) |
---|
914 | { |
---|
915 | updateID = sql_get_int_field(db, "SELECT UPDATE_ID from SETTINGS"); |
---|
916 | } |
---|
917 | i = db_upgrade(db); |
---|
918 | if( i != 0 ) |
---|
919 | { |
---|
920 | if( i < 0 ) |
---|
921 | { |
---|
922 | DPRINTF(E_WARN, L_GENERAL, "Creating new database...\n"); |
---|
923 | } |
---|
924 | else |
---|
925 | { |
---|
926 | DPRINTF(E_WARN, L_GENERAL, "Database version mismatch; need to recreate...\n"); |
---|
927 | } |
---|
928 | sqlite3_close(db); |
---|
929 | char *cmd; |
---|
930 | asprintf(&cmd, "rm -rf %s/files.db %s/art_cache", db_path, db_path); |
---|
931 | system(cmd); |
---|
932 | free(cmd); |
---|
933 | open_db(); |
---|
934 | if( CreateDatabase() != 0 ) |
---|
935 | { |
---|
936 | DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n"); |
---|
937 | } |
---|
938 | #if USE_FORK |
---|
939 | scanning = 1; |
---|
940 | sqlite3_close(db); |
---|
941 | scanner_pid = fork(); |
---|
942 | open_db(); |
---|
943 | if( !scanner_pid ) // child (scanner) process |
---|
944 | { |
---|
945 | start_scanner(); |
---|
946 | sqlite3_close(db); |
---|
947 | media_path = media_dirs; |
---|
948 | art_names = album_art_names; |
---|
949 | while( media_path ) |
---|
950 | { |
---|
951 | free(media_path->path); |
---|
952 | last_path = media_path; |
---|
953 | media_path = media_path->next; |
---|
954 | free(last_path); |
---|
955 | } |
---|
956 | while( art_names ) |
---|
957 | { |
---|
958 | free(art_names->name); |
---|
959 | last_name = art_names; |
---|
960 | art_names = art_names->next; |
---|
961 | free(last_name); |
---|
962 | } |
---|
963 | freeoptions(); |
---|
964 | exit(EXIT_SUCCESS); |
---|
965 | } |
---|
966 | #else |
---|
967 | start_scanner(); |
---|
968 | #endif |
---|
969 | } |
---|
970 | if( sqlite3_threadsafe() && sqlite3_libversion_number() >= 3005001 && |
---|
971 | GETFLAG(INOTIFY_MASK) && pthread_create(&inotify_thread, NULL, start_inotify, NULL) ) |
---|
972 | { |
---|
973 | DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify.\n"); |
---|
974 | } |
---|
975 | |
---|
976 | sudp = OpenAndConfSSDPReceiveSocket(n_lan_addr, lan_addr); |
---|
977 | if(sudp < 0) |
---|
978 | { |
---|
979 | DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n"); |
---|
980 | if(SubmitServicesToMiniSSDPD(lan_addr[0].str, runtime_vars.port) < 0) { |
---|
981 | DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING"); |
---|
982 | return 1; |
---|
983 | } |
---|
984 | } |
---|
985 | /* open socket for HTTP connections. Listen on the 1st LAN address */ |
---|
986 | shttpl = OpenAndConfHTTPSocket(runtime_vars.port); |
---|
987 | if(shttpl < 0) |
---|
988 | { |
---|
989 | DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n"); |
---|
990 | } |
---|
991 | DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port); |
---|
992 | |
---|
993 | /* open socket for sending notifications */ |
---|
994 | if(OpenAndConfSSDPNotifySockets(snotify) < 0) |
---|
995 | { |
---|
996 | DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending SSDP notify " |
---|
997 | "messages. EXITING\n"); |
---|
998 | } |
---|
999 | |
---|
1000 | #ifdef TIVO_SUPPORT |
---|
1001 | if( GETFLAG(TIVO_MASK) ) |
---|
1002 | { |
---|
1003 | DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n"); |
---|
1004 | /* Add TiVo-specific randomize function to sqlite */ |
---|
1005 | if( sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL) != SQLITE_OK ) |
---|
1006 | { |
---|
1007 | DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n"); |
---|
1008 | } |
---|
1009 | /* open socket for sending Tivo notifications */ |
---|
1010 | sbeacon = OpenAndConfTivoBeaconSocket(); |
---|
1011 | if(sbeacon < 0) |
---|
1012 | { |
---|
1013 | DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " |
---|
1014 | "messages. EXITING\n"); |
---|
1015 | } |
---|
1016 | tivo_bcast.sin_family = AF_INET; |
---|
1017 | tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); |
---|
1018 | tivo_bcast.sin_port = htons(2190); |
---|
1019 | } |
---|
1020 | else |
---|
1021 | { |
---|
1022 | sbeacon = -1; |
---|
1023 | } |
---|
1024 | #endif |
---|
1025 | |
---|
1026 | SendSSDPGoodbye(snotify, n_lan_addr); |
---|
1027 | |
---|
1028 | /* main loop */ |
---|
1029 | while(!quitting) |
---|
1030 | { |
---|
1031 | /* Check if we need to send SSDP NOTIFY messages and do it if |
---|
1032 | * needed */ |
---|
1033 | if(gettimeofday(&timeofday, 0) < 0) |
---|
1034 | { |
---|
1035 | DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); |
---|
1036 | timeout.tv_sec = runtime_vars.notify_interval; |
---|
1037 | timeout.tv_usec = 0; |
---|
1038 | } |
---|
1039 | else |
---|
1040 | { |
---|
1041 | /* the comparaison is not very precise but who cares ? */ |
---|
1042 | if(timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) |
---|
1043 | { |
---|
1044 | SendSSDPNotifies2(snotify, |
---|
1045 | (unsigned short)runtime_vars.port, |
---|
1046 | (runtime_vars.notify_interval << 1)+10); |
---|
1047 | memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval)); |
---|
1048 | timeout.tv_sec = runtime_vars.notify_interval; |
---|
1049 | timeout.tv_usec = 0; |
---|
1050 | } |
---|
1051 | else |
---|
1052 | { |
---|
1053 | timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval |
---|
1054 | - timeofday.tv_sec; |
---|
1055 | if(timeofday.tv_usec > lastnotifytime.tv_usec) |
---|
1056 | { |
---|
1057 | timeout.tv_usec = 1000000 + lastnotifytime.tv_usec |
---|
1058 | - timeofday.tv_usec; |
---|
1059 | timeout.tv_sec--; |
---|
1060 | } |
---|
1061 | else |
---|
1062 | { |
---|
1063 | timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec; |
---|
1064 | } |
---|
1065 | } |
---|
1066 | #ifdef TIVO_SUPPORT |
---|
1067 | if( GETFLAG(TIVO_MASK) ) |
---|
1068 | { |
---|
1069 | if(timeofday.tv_sec >= (lastbeacontime.tv_sec + beacon_interval)) |
---|
1070 | { |
---|
1071 | sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); |
---|
1072 | memcpy(&lastbeacontime, &timeofday, sizeof(struct timeval)); |
---|
1073 | if( timeout.tv_sec > beacon_interval ) |
---|
1074 | { |
---|
1075 | timeout.tv_sec = beacon_interval; |
---|
1076 | timeout.tv_usec = 0; |
---|
1077 | } |
---|
1078 | /* Beacons should be sent every 5 seconds or so for the first minute, |
---|
1079 | * then every minute or so thereafter. */ |
---|
1080 | if( beacon_interval == 5 && (timeofday.tv_sec - startup_time) > 60 ) |
---|
1081 | { |
---|
1082 | beacon_interval = 60; |
---|
1083 | } |
---|
1084 | } |
---|
1085 | else if( timeout.tv_sec > (lastbeacontime.tv_sec + beacon_interval + 1 - timeofday.tv_sec) ) |
---|
1086 | { |
---|
1087 | timeout.tv_sec = lastbeacontime.tv_sec + beacon_interval - timeofday.tv_sec; |
---|
1088 | } |
---|
1089 | } |
---|
1090 | #endif |
---|
1091 | } |
---|
1092 | |
---|
1093 | if( scanning ) |
---|
1094 | { |
---|
1095 | if( !scanner_pid || kill(scanner_pid, 0) ) |
---|
1096 | { |
---|
1097 | scanning = 0; |
---|
1098 | updateID++; |
---|
1099 | } |
---|
1100 | } |
---|
1101 | |
---|
1102 | /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ |
---|
1103 | FD_ZERO(&readset); |
---|
1104 | |
---|
1105 | if (sudp >= 0) |
---|
1106 | { |
---|
1107 | FD_SET(sudp, &readset); |
---|
1108 | max_fd = MAX(max_fd, sudp); |
---|
1109 | } |
---|
1110 | |
---|
1111 | if (shttpl >= 0) |
---|
1112 | { |
---|
1113 | FD_SET(shttpl, &readset); |
---|
1114 | max_fd = MAX(max_fd, shttpl); |
---|
1115 | } |
---|
1116 | #ifdef TIVO_SUPPORT |
---|
1117 | if (sbeacon >= 0) |
---|
1118 | { |
---|
1119 | FD_SET(sbeacon, &readset); |
---|
1120 | max_fd = MAX(max_fd, sbeacon); |
---|
1121 | } |
---|
1122 | #endif |
---|
1123 | i = 0; /* active HTTP connections count */ |
---|
1124 | for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) |
---|
1125 | { |
---|
1126 | if((e->socket >= 0) && (e->state <= 2)) |
---|
1127 | { |
---|
1128 | FD_SET(e->socket, &readset); |
---|
1129 | max_fd = MAX(max_fd, e->socket); |
---|
1130 | i++; |
---|
1131 | } |
---|
1132 | } |
---|
1133 | #ifdef DEBUG |
---|
1134 | /* for debug */ |
---|
1135 | if(i > 1) |
---|
1136 | { |
---|
1137 | DPRINTF(E_DEBUG, L_GENERAL, "%d active incoming HTTP connections\n", i); |
---|
1138 | } |
---|
1139 | #endif |
---|
1140 | FD_ZERO(&writeset); |
---|
1141 | upnpevents_selectfds(&readset, &writeset, &max_fd); |
---|
1142 | |
---|
1143 | if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0) |
---|
1144 | { |
---|
1145 | if(quitting) goto shutdown; |
---|
1146 | DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno)); |
---|
1147 | DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n"); |
---|
1148 | } |
---|
1149 | upnpevents_processfds(&readset, &writeset); |
---|
1150 | /* process SSDP packets */ |
---|
1151 | if(sudp >= 0 && FD_ISSET(sudp, &readset)) |
---|
1152 | { |
---|
1153 | /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ |
---|
1154 | ProcessSSDPRequest(sudp, (unsigned short)runtime_vars.port); |
---|
1155 | } |
---|
1156 | #ifdef TIVO_SUPPORT |
---|
1157 | if(sbeacon >= 0 && FD_ISSET(sbeacon, &readset)) |
---|
1158 | { |
---|
1159 | /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ |
---|
1160 | ProcessTiVoBeacon(sbeacon); |
---|
1161 | } |
---|
1162 | #endif |
---|
1163 | /* increment SystemUpdateID if the content database has changed, |
---|
1164 | * and if there is an active HTTP connection, at most once every 2 seconds */ |
---|
1165 | if( i && (timeofday.tv_sec >= (lastupdatetime + 2)) ) |
---|
1166 | { |
---|
1167 | if( scanning || sqlite3_total_changes(db) != last_changecnt ) |
---|
1168 | { |
---|
1169 | updateID++; |
---|
1170 | last_changecnt = sqlite3_total_changes(db); |
---|
1171 | upnp_event_var_change_notify(EContentDirectory); |
---|
1172 | lastupdatetime = timeofday.tv_sec; |
---|
1173 | } |
---|
1174 | } |
---|
1175 | /* process active HTTP connections */ |
---|
1176 | for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) |
---|
1177 | { |
---|
1178 | if( (e->socket >= 0) && (e->state <= 2) |
---|
1179 | &&(FD_ISSET(e->socket, &readset)) ) |
---|
1180 | { |
---|
1181 | Process_upnphttp(e); |
---|
1182 | } |
---|
1183 | } |
---|
1184 | /* process incoming HTTP connections */ |
---|
1185 | if(shttpl >= 0 && FD_ISSET(shttpl, &readset)) |
---|
1186 | { |
---|
1187 | int shttp; |
---|
1188 | socklen_t clientnamelen; |
---|
1189 | struct sockaddr_in clientname; |
---|
1190 | clientnamelen = sizeof(struct sockaddr_in); |
---|
1191 | shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); |
---|
1192 | if(shttp<0) |
---|
1193 | { |
---|
1194 | DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno)); |
---|
1195 | } |
---|
1196 | else |
---|
1197 | { |
---|
1198 | struct upnphttp * tmp = 0; |
---|
1199 | DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n", |
---|
1200 | inet_ntoa(clientname.sin_addr), |
---|
1201 | ntohs(clientname.sin_port) ); |
---|
1202 | /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) { |
---|
1203 | DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n"); |
---|
1204 | }*/ |
---|
1205 | /* Create a new upnphttp object and add it to |
---|
1206 | * the active upnphttp object list */ |
---|
1207 | tmp = New_upnphttp(shttp); |
---|
1208 | if(tmp) |
---|
1209 | { |
---|
1210 | tmp->clientaddr = clientname.sin_addr; |
---|
1211 | LIST_INSERT_HEAD(&upnphttphead, tmp, entries); |
---|
1212 | } |
---|
1213 | else |
---|
1214 | { |
---|
1215 | DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n"); |
---|
1216 | close(shttp); |
---|
1217 | } |
---|
1218 | } |
---|
1219 | } |
---|
1220 | /* delete finished HTTP connections */ |
---|
1221 | for(e = upnphttphead.lh_first; e != NULL; ) |
---|
1222 | { |
---|
1223 | next = e->entries.le_next; |
---|
1224 | if(e->state >= 100) |
---|
1225 | { |
---|
1226 | LIST_REMOVE(e, entries); |
---|
1227 | Delete_upnphttp(e); |
---|
1228 | } |
---|
1229 | e = next; |
---|
1230 | } |
---|
1231 | } |
---|
1232 | |
---|
1233 | shutdown: |
---|
1234 | /* kill the scanner */ |
---|
1235 | if( scanning && scanner_pid ) |
---|
1236 | { |
---|
1237 | kill(scanner_pid, 9); |
---|
1238 | } |
---|
1239 | /* close out open sockets */ |
---|
1240 | while(upnphttphead.lh_first != NULL) |
---|
1241 | { |
---|
1242 | e = upnphttphead.lh_first; |
---|
1243 | LIST_REMOVE(e, entries); |
---|
1244 | Delete_upnphttp(e); |
---|
1245 | } |
---|
1246 | |
---|
1247 | if (sudp >= 0) close(sudp); |
---|
1248 | if (shttpl >= 0) close(shttpl); |
---|
1249 | #ifdef TIVO_SUPPORT |
---|
1250 | if (sbeacon >= 0) close(sbeacon); |
---|
1251 | #endif |
---|
1252 | |
---|
1253 | if(SendSSDPGoodbye(snotify, n_lan_addr) < 0) |
---|
1254 | { |
---|
1255 | DPRINTF(E_ERROR, L_GENERAL, "Failed to broadcast good-bye notifications\n"); |
---|
1256 | } |
---|
1257 | for(i=0; i<n_lan_addr; i++) |
---|
1258 | close(snotify[i]); |
---|
1259 | |
---|
1260 | if( inotify_thread ) |
---|
1261 | pthread_join(inotify_thread, NULL); |
---|
1262 | |
---|
1263 | sql_exec(db, "UPDATE SETTINGS set UPDATE_ID = %u", updateID); |
---|
1264 | sqlite3_close(db); |
---|
1265 | |
---|
1266 | media_path = media_dirs; |
---|
1267 | art_names = album_art_names; |
---|
1268 | while( media_path ) |
---|
1269 | { |
---|
1270 | free(media_path->path); |
---|
1271 | last_path = media_path; |
---|
1272 | media_path = media_path->next; |
---|
1273 | free(last_path); |
---|
1274 | } |
---|
1275 | while( art_names ) |
---|
1276 | { |
---|
1277 | free(art_names->name); |
---|
1278 | last_name = art_names; |
---|
1279 | art_names = art_names->next; |
---|
1280 | free(last_name); |
---|
1281 | } |
---|
1282 | |
---|
1283 | if(unlink(pidfilename) < 0) |
---|
1284 | { |
---|
1285 | DPRINTF(E_ERROR, L_GENERAL, "Failed to remove pidfile %s: %s\n", pidfilename, strerror(errno)); |
---|
1286 | } |
---|
1287 | |
---|
1288 | freeoptions(); |
---|
1289 | |
---|
1290 | exit(EXIT_SUCCESS); |
---|
1291 | } |
---|
1292 | |
---|