1/* 2 * $Id: main.c,v 1.1 2009-06-30 02:31:08 steven Exp $ 3 * Driver for multi-threaded daap server 4 * 5 * Copyright (C) 2003 Ron Pedde (ron@pedde.com) 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22/** 23 * \file main.c 24 * 25 * Driver for mt-daapd, including the main() function. This 26 * is responsible for kicking off the initial mp3 scan, starting 27 * up the signal handler, starting up the webserver, and waiting 28 * around for external events to happen (like a request to rescan, 29 * or a background rescan to take place.) 30 * 31 * It also contains the daap handling callback for the webserver. 32 * This should almost certainly be somewhere else, and is in 33 * desparate need of refactoring, but somehow continues to be in 34 * this file. 35 * 36 * \todo Refactor daap_handler() 37 */ 38 39/** \mainpage mt-daapd 40 * \section about_section About 41 * 42 * This is mt-daapd, an attempt to create an iTunes server for 43 * linux and other POSIXish systems. Maybe even Windows with cygwin, 44 * eventually. 45 * 46 * You might check these locations for more info: 47 * - <a href="http://sf.net/projects/mt-daapd">Project page on SourceForge</a> 48 * - <a href="http://mt-daapd.sf.net">Home page</a> 49 * 50 */ 51 52#ifdef HAVE_CONFIG_H 53#include "config.h" 54#endif 55 56#include <errno.h> 57#include <fcntl.h> 58#include <grp.h> 59#include <limits.h> 60#include <pthread.h> 61#include <pwd.h> 62#include <restart.h> 63#include <signal.h> 64#include <stdio.h> 65#include <stdlib.h> 66#include <string.h> 67#include <unistd.h> 68 69#include <sys/stat.h> 70#include <sys/types.h> 71#include <sys/wait.h> 72 73#include "configfile.h" 74#include "db-memory.h" 75#include "daap.h" 76#include "daap-proto.h" 77#include "err.h" 78#include "mp3-scanner.h" 79#include "webserver.h" 80#include "playlist.h" 81#include "dynamic-art.h" 82 83#ifndef WITHOUT_MDNS 84#include "rend.h" 85#endif 86 87/** 88 * Where the default configfile is. On the NSLU2 running unslung, 89 * thats in /opt, not /etc. */ 90#ifndef DEFAULT_CONFIGFILE 91#ifdef NSLU2 92#define DEFAULT_CONFIGFILE "/opt/etc/mt-daapd/mt-daapd.conf" 93#else 94#define DEFAULT_CONFIGFILE "/etc/mt-daapd.conf" 95#endif 96#endif 97 98/** Where to dump the pidfile */ 99#ifndef PIDFILE 100#define PIDFILE "/var/run/mt-daapd.pid" 101#endif 102 103/** You say po-tay-to, I say po-tat-o */ 104#ifndef SIGCLD 105# define SIGCLD SIGCHLD 106#endif 107 108/** Seconds to sleep before checking for a shutdown or reload */ 109#define MAIN_SLEEP_INTERVAL 2 110 111/** Let's hope if you have no atoll, you only have 32 bit inodes... */ 112#if !HAVE_ATOLL 113# define atoll(a) atol(a) 114#endif 115 116/* 117 * Globals 118 */ 119CONFIG config; /**< Main configuration structure, as read from configfile */ 120 121/* 122 * Forwards 123 */ 124static int daemon_start(void); 125static void write_pid_file(void); 126static void usage(char *program); 127static void *signal_handler(void *arg); 128static int start_signal_handler(pthread_t *handler_tid); 129static void daap_handler(WS_CONNINFO *pwsc); 130static int daap_auth(char *username, char *password); 131 132/** 133 * Handles authentication for the daap server. This isn't the 134 * authenticator for the web admin page, but rather the iTunes 135 * authentication when trying to connect to the server. Note that most 136 * of this is actually handled in the web server registration, which 137 * decides when to apply the authentication or not. If you mess with 138 * when and where the webserver applies auth or not, you'll likely 139 * break something. It seems that some requests must be authed, and others 140 * not. If you apply authentication somewhere that iTunes doesn't expect 141 * it, it happily disconnects. 142 * 143 * \param username The username passed by iTunes 144 * \param password The password passed by iTunes 145 * \returns 1 if auth successful, 0 otherwise 146 */ 147int daap_auth(char *username, char *password) { 148 if((password == NULL) && 149 ((config.readpassword == NULL) || (strlen(config.readpassword)==0))) 150 return 1; 151 152 if(password == NULL) 153 return 0; 154 155 return !strcasecmp(password,config.readpassword); 156} 157 158/** 159 * This handles requests that are daap-related. For example, 160 * /server-info, /login, etc. This should really be split up 161 * into multiple functions, and perhaps moved into daap.c 162 * 163 * \param pwsc Webserver connection info, passed from the webserver 164 * 165 * \todo Decomplexify this! 166 */ 167void daap_handler(WS_CONNINFO *pwsc) { 168 int close; 169 DAAP_BLOCK *root; 170 int clientrev; 171 172 /* for the /databases URI */ 173 char *uri; 174 unsigned long int db_index; 175 unsigned long int playlist_index; 176 unsigned long int item=0; 177 char *first, *last; 178 char* index = 0; 179 int streaming=0; 180 int compress =0; 181 int start_time; 182 int end_time; 183 int bytes_written; 184 185 MP3FILE *pmp3; 186 int file_fd; 187 int session_id=0; 188 189 int img_fd; 190 struct stat sb; 191 long img_size; 192 193 off_t offset=0; 194 off_t real_len; 195 off_t file_len; 196 197 int bytes_copied=0; 198 199 GZIP_STREAM *gz; 200 201 close=pwsc->close; 202 pwsc->close=1; /* in case we have any errors */ 203 root=NULL; 204 205 ws_addresponseheader(pwsc,"Accept-Ranges","bytes"); 206 ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/%s",VERSION); 207 ws_addresponseheader(pwsc,"Content-Type","application/x-dmap-tagged"); 208 209 if(ws_getvar(pwsc,"session-id")) { 210 session_id=atoi(ws_getvar(pwsc,"session-id")); 211 } 212 213 if(!strcasecmp(pwsc->uri,"/server-info")) { 214 config_set_status(pwsc,session_id,"Sending server info"); 215 root=daap_response_server_info(config.servername, 216 ws_getrequestheader(pwsc,"Client-DAAP-Version")); 217 } else if (!strcasecmp(pwsc->uri,"/content-codes")) { 218 config_set_status(pwsc,session_id,"Sending content codes"); 219 root=daap_response_content_codes(); 220 } else if (!strcasecmp(pwsc->uri,"/login")) { 221 config_set_status(pwsc,session_id,"Logging in"); 222 root=daap_response_login(pwsc->hostname); 223 } else if (!strcasecmp(pwsc->uri,"/update")) { 224 if(!ws_getvar(pwsc,"delta")) { /* first check */ 225 clientrev=db_version() - 1; 226 config_set_status(pwsc,session_id,"Sending database"); 227 } else { 228 clientrev=atoi(ws_getvar(pwsc,"delta")); 229 config_set_status(pwsc,session_id,"Waiting for DB updates"); 230 } 231 root=daap_response_update(pwsc->fd,clientrev); 232 if((ws_getvar(pwsc,"delta")) && (root==NULL)) { 233 DPRINTF(E_LOG,L_WS,"Client %s disconnected\n",pwsc->hostname); 234 config_set_status(pwsc,session_id,NULL); 235 pwsc->close=1; 236 return; 237 } 238 } else if (!strcasecmp(pwsc->uri,"/logout")) { 239 config_set_status(pwsc,session_id,NULL); 240 ws_returnerror(pwsc,204,"Logout Successful"); 241 return; 242 } else if(strcmp(pwsc->uri,"/databases")==0) { 243 config_set_status(pwsc,session_id,"Sending database info"); 244 root=daap_response_dbinfo(config.servername); 245 if(0 != (index = ws_getvar(pwsc, "index"))) 246 daap_handle_index(root, index); 247 } else if(strncmp(pwsc->uri,"/databases/",11) == 0) { 248 249 /* the /databases/ uri will either be: 250 * 251 * /databases/id/items, which returns items in a db 252 * /databases/id/containers, which returns a container 253 * /databases/id/containers/id/items, which returns playlist elements 254 * /databases/id/items/id.mp3, to spool an mp3 255 * /databases/id/browse/category 256 */ 257 258 uri = strdup(pwsc->uri); 259 first=(char*)&uri[11]; 260 last=first; 261 while((*last) && (*last != '/')) { 262 last++; 263 } 264 265 if(*last) { 266 *last='\0'; 267 db_index=atoll(first); 268 269 last++; 270 271 if(strncasecmp(last,"items/",6)==0) { 272 /* streaming */ 273 first=last+6; 274 while((*last) && (*last != '.')) 275 last++; 276 277 if(*last == '.') { 278 *last='\0'; 279 item=atoll(first); 280 streaming=1; 281 DPRINTF(E_DBG,L_DAAP|L_WS,"Streaming request for id %lu\n",item); 282 } 283 free(uri); 284 } else if (strncasecmp(last,"items",5)==0) { 285 /* songlist */ 286 free(uri); 287 // pass the meta field request for processing 288 // pass the query request for processing 289 root=daap_response_songlist(ws_getvar(pwsc,"meta"), 290 ws_getvar(pwsc,"query")); 291 config_set_status(pwsc,session_id,"Sending songlist"); 292 } else if (strncasecmp(last,"containers/",11)==0) { 293 /* playlist elements */ 294 first=last + 11; 295 last=first; 296 while((*last) && (*last != '/')) { 297 last++; 298 } 299 300 if(*last) { 301 *last='\0'; 302 playlist_index=atoll(first); 303 // pass the meta list info for processing 304 root=daap_response_playlist_items(playlist_index, 305 ws_getvar(pwsc,"meta"), 306 ws_getvar(pwsc,"query")); 307 } 308 free(uri); 309 config_set_status(pwsc,session_id,"Sending playlist info"); 310 } else if (strncasecmp(last,"containers",10)==0) { 311 /* list of playlists */ 312 free(uri); 313 root=daap_response_playlists(config.servername); 314 config_set_status(pwsc,session_id,"Sending playlist info"); 315 } else if (strncasecmp(last,"browse/",7)==0) { 316 config_set_status(pwsc,session_id,"Compiling browse info"); 317 root = daap_response_browse(last + 7, 318 ws_getvar(pwsc, "filter")); 319 config_set_status(pwsc,session_id,"Sending browse info"); 320 free(uri); 321 } 322 } 323 324 // prune the full list if an index range was specified 325 if(0 != (index = ws_getvar(pwsc, "index"))) 326 daap_handle_index(root, index); 327 } 328 329 if((!root)&&(!streaming)) { 330 DPRINTF(E_DBG,L_WS|L_DAAP,"Bad request -- root=%x, streaming=%d\n",root,streaming); 331 ws_returnerror(pwsc,400,"Invalid Request"); 332 config_set_status(pwsc,session_id,NULL); 333 return; 334 } 335 336 pwsc->close=close; 337 338 if(!streaming) { 339 DPRINTF(E_DBG,L_WS,"Satisfying request\n"); 340 341 if((config.compress) && ws_testrequestheader(pwsc,"Accept-Encoding","gzip") && root->reported_size >= 1000) { 342 compress=1; 343 } 344 345 DPRINTF(E_DBG,L_WS|L_DAAP,"Serializing\n"); 346 start_time = time(NULL); 347 if (compress) { 348 DPRINTF(E_DBG,L_WS|L_DAAP,"Using compression: %s\n", pwsc->uri); 349 gz = gzip_alloc(); 350 daap_serialize(root,pwsc->fd,gz); 351 gzip_compress(gz); 352 bytes_written = gz->bytes_out; 353 ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); 354 ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written); 355 ws_addresponseheader(pwsc,"Content-Encoding","gzip"); 356 DPRINTF(E_DBG,L_WS,"Emitting headers\n"); 357 ws_emitheaders(pwsc); 358 if (gzip_close(gz,pwsc->fd) != bytes_written) { 359 DPRINTF(E_LOG,L_WS|L_DAAP,"Error compressing data\n"); 360 } 361 DPRINTF(E_DBG,L_WS|L_DAAP,"Compression ratio: %f\n",((double) bytes_written)/(8.0 + root->reported_size)) 362 } 363 else { 364 bytes_written = root->reported_size + 8; 365 ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written); 366 ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); 367 DPRINTF(E_DBG,L_WS,"Emitting headers\n"); 368 ws_emitheaders(pwsc); 369 daap_serialize(root,pwsc->fd,NULL); 370 } 371 end_time = time(NULL); 372 DPRINTF(E_DBG,L_WS|L_DAAP,"Sent %d bytes in %d seconds\n",bytes_written,end_time-start_time); 373 DPRINTF(E_DBG,L_WS|L_DAAP,"Done, freeing\n"); 374 daap_free(root); 375 } else { 376 /* stream out the song */ 377 pwsc->close=1; 378 379 if(ws_getrequestheader(pwsc,"range")) { 380 offset=(off_t)atol(ws_getrequestheader(pwsc,"range") + 6); 381 } 382 383 pmp3=db_find(item); 384 if(!pmp3) { 385 DPRINTF(E_LOG,L_DAAP|L_WS|L_DB,"Could not find requested item %lu\n",item); 386 ws_returnerror(pwsc,404,"File Not Found"); 387 } else { 388 /* got the file, let's open and serve it */ 389 file_fd=r_open2(pmp3->path,O_RDONLY); 390 if(file_fd == -1) { 391 pwsc->error=errno; 392 DPRINTF(E_WARN,L_WS,"Thread %d: Error opening %s: %s\n", 393 pwsc->threadno,pmp3->path,strerror(errno)); 394 ws_returnerror(pwsc,404,"Not found"); 395 config_set_status(pwsc,session_id,NULL); 396 db_dispose(pmp3); 397 free(pmp3); 398 } else { 399 real_len=lseek(file_fd,0,SEEK_END); 400 lseek(file_fd,0,SEEK_SET); 401 402 /* Re-adjust content length for cover art */ 403 if((config.artfilename) && 404 ((img_fd=da_get_image_fd(pmp3->path)) != -1)) { 405 fstat(img_fd, &sb); 406 img_size = sb.st_size; 407 408 if (strncasecmp(pmp3->type,"mp3",4) ==0) { 409 /*PENDING*/ 410 } else if (strncasecmp(pmp3->type, "m4a", 4) == 0) { 411 real_len += img_size + 24; 412 413 if (offset > img_size + 24) { 414 offset -= img_size + 24; 415 } 416 } 417 } 418 419 file_len = real_len - offset; 420 421 DPRINTF(E_DBG,L_WS,"Thread %d: Length of file (remaining) is %ld\n", 422 pwsc->threadno,(long)file_len); 423 424 // DWB: fix content-type to correctly reflect data 425 // content type (dmap tagged) should only be used on 426 // dmap protocol requests, not the actually song data 427 if(pmp3->type) 428 ws_addresponseheader(pwsc,"Content-Type","audio/%s",pmp3->type); 429 430 ws_addresponseheader(pwsc,"Content-Length","%ld",(long)file_len); 431 ws_addresponseheader(pwsc,"Connection","Close"); 432 433 434 if(!offset) 435 ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); 436 else { 437 ws_addresponseheader(pwsc,"Content-Range","bytes %ld-%ld/%ld", 438 (long)offset,(long)real_len, 439 (long)real_len+1); 440 ws_writefd(pwsc,"HTTP/1.1 206 Partial Content\r\n"); 441 } 442 443 ws_emitheaders(pwsc); 444 445 config_set_status(pwsc,session_id,"Streaming file '%s'",pmp3->fname); 446 DPRINTF(E_LOG,L_WS,"Session %d: Streaming file '%s' to %s (offset %d)\n", 447 session_id,pmp3->fname, pwsc->hostname,(long)offset); 448 449 if(!offset) 450 config.stats.songs_served++; /* FIXME: remove stat races */ 451 452 if((config.artfilename) && 453 (!offset) && 454 ((img_fd=da_get_image_fd(pmp3->path)) != -1)) { 455 if (strncasecmp(pmp3->type,"mp3",4) ==0) { 456 DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n", 457 pmp3->fname, img_fd); 458 da_attach_image(img_fd, pwsc->fd, file_fd, offset); 459 } else if (strncasecmp(pmp3->type, "m4a", 4) == 0) { 460 DPRINTF(E_INF,L_WS|L_ART,"Dynamic add artwork to %s (fd %d)\n", 461 pmp3->fname, img_fd); 462 da_aac_attach_image(img_fd, pwsc->fd, file_fd, offset); 463 } 464 } else if(offset) { 465 DPRINTF(E_INF,L_WS,"Seeking to offset %ld\n",(long)offset); 466 lseek(file_fd,offset,SEEK_SET); 467 } 468 469 if((bytes_copied=copyfile(file_fd,pwsc->fd)) == -1) { 470 DPRINTF(E_INF,L_WS,"Error copying file to remote... %s\n", 471 strerror(errno)); 472 } else { 473 DPRINTF(E_INF,L_WS,"Finished streaming file to remote: %d bytes\n", 474 bytes_copied); 475 } 476 477 config_set_status(pwsc,session_id,NULL); 478 r_close(file_fd); 479 db_dispose(pmp3); 480 free(pmp3); 481 } 482 } 483 } 484 485 DPRINTF(E_DBG,L_WS|L_DAAP,"Finished serving DAAP response\n"); 486 487 return; 488} 489 490/** 491 * Fork and exit. Stolen pretty much straight from Stevens. 492 */ 493int daemon_start(void) { 494 int childpid, fd; 495 496 signal(SIGTTOU, SIG_IGN); 497 signal(SIGTTIN, SIG_IGN); 498 signal(SIGTSTP, SIG_IGN); 499 500 // Fork and exit 501 if ((childpid = fork()) < 0) { 502 fprintf(stderr, "Can't fork!\n"); 503 return -1; 504 } else if (childpid > 0) 505 exit(0); 506 507#ifdef SETPGRP_VOID 508 setpgrp(); 509#else 510 setpgrp(0,0); 511#endif 512 513#ifdef TIOCNOTTY 514 if ((fd = open("/dev/tty", O_RDWR)) >= 0) { 515 ioctl(fd, TIOCNOTTY, (char *) NULL); 516 close(fd); 517 } 518#endif 519 520 if((fd = open("/dev/null", O_RDWR, 0)) != -1) { 521 dup2(fd, STDIN_FILENO); 522 dup2(fd, STDOUT_FILENO); 523 dup2(fd, STDERR_FILENO); 524 if (fd > 2) 525 close(fd); 526 } 527 528 /* 529 for (fd = 0; fd < FOPEN_MAX; fd++) 530 close(fd); 531 */ 532 533 errno = 0; 534 535 chdir("/"); 536 umask(0); 537 538 return 0; 539} 540 541/** 542 * Print usage information to stdout 543 * 544 * \param program name of program (argv[0]) 545 */ 546void usage(char *program) { 547 printf("Usage: %s [options]\n\n",program); 548 printf("Options:\n"); 549 printf(" -d <number> Debuglevel (0-9)\n"); 550 printf(" -D <mod,mod..> Debug modules\n"); 551 printf(" -m Disable mDNS\n"); 552 printf(" -c <file> Use configfile specified\n"); 553 printf(" -p Parse playlist file\n"); 554 printf(" -f Run in foreground\n"); 555 printf(" -y Yes, go ahead and run as non-root user\n"); 556 printf("\n\n"); 557 printf("Valid debug modules:\n"); 558 printf(" config,webserver,database,scan,query,index,browse\n"); 559 printf(" playlist,art,daap,main,rend,misc\n"); 560 printf("\n\n"); 561} 562 563/** 564 * Drop privs. This allows mt-daapd to run as a non-privileged user. 565 * Hopefully this will limit the damage it could do if exploited 566 * remotely. Note that only the user need be specified. GID 567 * is set to the primary group of the user. 568 * 569 * \param user user to run as (or UID) 570 */ 571int drop_privs(char *user) { 572 int err; 573 struct passwd *pw=NULL; 574 575 /* drop privs */ 576 if(getuid() == (uid_t)0) { 577 //if(atoi(user)) { 578 // pw=getpwuid((uid_t)atoi(user)); /* doh! */ 579 //} else { 580 pw=getpwnam(config.runas); 581 //} 582 583 if(pw) { 584 if(initgroups(user,pw->pw_gid) != 0 || 585 setgid(pw->pw_gid) != 0 || 586 setuid(pw->pw_uid) != 0) { 587 err=errno; 588 fprintf(stderr,"Couldn't change to %s, gid=%d, uid=%d\n", 589 user,pw->pw_gid, pw->pw_uid); 590 errno=err; 591 return -1; 592 } 593 } else { 594 err=errno; 595 fprintf(stderr,"Couldn't lookup user %s\n",user); 596 errno=err; 597 return -1; 598 } 599 } 600 601 return 0; 602} 603 604/** 605 * Wait for signals and flag the main process. This is 606 * a thread handler for the signal processing thread. It 607 * does absolutely nothing except wait for signals. The rest 608 * of the threads are running with signals blocked, so this thread 609 * is guaranteed to catch all the signals. It sets flags in 610 * the config structure that the main thread looks for. Specifically, 611 * the stop flag (from an INT signal), and the reload flag (from HUP). 612 * \param arg NULL, but required of a thread procedure 613 */ 614void *signal_handler(void *arg) { 615 sigset_t intmask; 616 int sig; 617 int status; 618 619 config.stop=0; 620 config.reload=0; 621 config.pid=getpid(); 622 623 DPRINTF(E_WARN,L_MAIN,"Signal handler started\n"); 624 625 while(!config.stop) { 626 if((sigemptyset(&intmask) == -1) || 627 (sigaddset(&intmask, SIGCLD) == -1) || 628 (sigaddset(&intmask, SIGINT) == -1) || 629 (sigaddset(&intmask, SIGHUP) == -1) || 630 (sigwait(&intmask, &sig) == -1)) { 631 DPRINTF(E_FATAL,L_MAIN,"Error waiting for signals. Aborting\n"); 632 } else { 633 /* process the signal */ 634 switch(sig) { 635 case SIGCLD: 636 DPRINTF(E_LOG,L_MAIN,"Got CLD signal. Reaping\n"); 637 while (wait(&status)) { 638 }; 639 break; 640 case SIGINT: 641 DPRINTF(E_LOG,L_MAIN,"Got INT signal. Notifying daap server.\n"); 642 config.stop=1; 643 return NULL; 644 break; 645 case SIGHUP: 646 DPRINTF(E_LOG,L_MAIN,"Got HUP signal. Notifying daap server.\n"); 647 config.reload=1; 648 break; 649 default: 650 DPRINTF(E_LOG,L_MAIN,"What am I doing here?\n"); 651 break; 652 } 653 } 654 } 655 656 return NULL; 657} 658 659/** 660 * Block signals, then start the signal handler. The 661 * signal handler started by spawning a new thread on 662 * signal_handler(). 663 * 664 * \returns 0 on success, -1 on failure with errno set 665 */ 666int start_signal_handler(pthread_t *handler_tid) { 667 int error; 668 sigset_t set; 669 670 if((sigemptyset(&set) == -1) || 671 (sigaddset(&set,SIGINT) == -1) || 672 (sigaddset(&set,SIGHUP) == -1) || 673 (sigaddset(&set,SIGCLD) == -1) || 674 (sigprocmask(SIG_BLOCK, &set, NULL) == -1)) { 675 DPRINTF(E_LOG,L_MAIN,"Error setting signal set\n"); 676 return -1; 677 } 678 679 if(error=pthread_create(handler_tid, NULL, signal_handler, NULL)) { 680 errno=error; 681 DPRINTF(E_LOG,L_MAIN,"Error creating signal_handler thread\n"); 682 return -1; 683 } 684 685 /* we'll not detach this... let's join it */ 686 //pthread_detach(handler_tid); 687 return 0; 688} 689 690/** 691 * Kick off the daap server and wait for events. 692 * 693 * This starts the initial db scan, sets up the signal 694 * handling, starts the webserver, then sits back and waits 695 * for events, as notified by the signal handler and the 696 * web interface. These events are communicated via flags 697 * in the config structure. 698 * 699 * \param argc count of command line arguments 700 * \param argv command line argument pointers 701 * \returns 0 on success, -1 otherwise 702 * 703 * \todo split out a ws_init and ws_start, so that the 704 * web space handlers can be registered before the webserver 705 * starts. 706 * 707 */ 708int main(int argc, char *argv[]) { 709 int option; 710 char *configfile=DEFAULT_CONFIGFILE; 711 WSCONFIG ws_config; 712 WSHANDLE server; 713 int parseonly=0; 714 int foreground=0; 715 int reload=0; 716 int start_time; 717 int end_time; 718 int rescan_counter=0; 719 int old_song_count; 720 int force_non_root=0; 721 pthread_t signal_tid; 722 723 int pid_fd; 724 FILE *pid_fp=NULL; 725 726 config.use_mdns=1; 727 err_debuglevel=1; 728 729 while((option=getopt(argc,argv,"D:d:c:mpfry")) != -1) { 730 switch(option) { 731 case 'd': 732 err_debuglevel=atoi(optarg); 733 break; 734 case 'D': 735 if(err_setdebugmask(optarg)) { 736 usage(argv[0]); 737 exit(EXIT_FAILURE); 738 } 739 break; 740 case 'f': 741 foreground=1; 742 break; 743 744 case 'c': 745 configfile=optarg; 746 break; 747 748 case 'm': 749 config.use_mdns=0; 750 break; 751 752 case 'p': 753 parseonly=1; 754 foreground=1; 755 break; 756 757 case 'r': 758 reload=1; 759 break; 760 761 case 'y': 762 force_non_root=1; 763 break; 764 765 default: 766 usage(argv[0]); 767 exit(EXIT_FAILURE); 768 break; 769 } 770 } 771 772 if((getuid()) && (!force_non_root)) { 773 fprintf(stderr,"You are not root. This is almost certainly wrong. If you are\n" 774 "sure you want to do this, use the -y command-line switch\n"); 775 exit(EXIT_FAILURE); 776 } 777 778 /* read the configfile, if specified, otherwise 779 * try defaults */ 780 config.stats.start_time=start_time=time(NULL); 781 782 if(config_read(configfile)) { 783 fprintf(stderr,"Error reading config file (%s)\n",configfile); 784 exit(EXIT_FAILURE); 785 } 786 787 if(!foreground) { 788 if(config.logfile) { 789 err_setdest(config.logfile,LOGDEST_LOGFILE); 790 } else { 791 err_setdest("mt-daapd",LOGDEST_SYSLOG); 792 } 793 } 794 795#ifndef WITHOUT_MDNS 796 if((config.use_mdns) && (!parseonly)) { 797 DPRINTF(E_LOG,L_MAIN,"Starting rendezvous daemon\n"); 798 if(rend_init(config.runas)) { 799 DPRINTF(E_FATAL,L_MAIN|L_REND,"Error in rend_init: %s\n",strerror(errno)); 800 } 801 } 802#endif 803 804 /* open the pidfile, so it can be written once we detach */ 805 if((!foreground) && (!force_non_root)) { 806 if(-1 == (pid_fd = open(PIDFILE,O_CREAT | O_WRONLY | O_TRUNC, 0644))) 807 DPRINTF(E_FATAL,L_MAIN,"Error opening pidfile (%s): %s\n",PIDFILE,strerror(errno)); 808 809 if(0 == (pid_fp = fdopen(pid_fd, "w"))) 810 DPRINTF(E_FATAL,L_MAIN,"fdopen: %s\n",strerror(errno)); 811 812 daemon_start(); 813 814 /* just to be on the safe side... */ 815 config.pid=0; 816 } 817 818 /* DWB: shouldn't this be done after dropping privs? */ 819 if(db_open(config.dbdir, reload)) 820 DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_open: %s\n",strerror(errno)); 821 822 823 // Drop privs here 824 if(drop_privs(config.runas)) { 825 DPRINTF(E_FATAL,L_MAIN,"Error in drop_privs: %s\n",strerror(errno)); 826 } 827 828 /* block signals and set up the signal handling thread */ 829 DPRINTF(E_LOG,L_MAIN,"Starting signal handler\n"); 830 if(start_signal_handler(&signal_tid)) { 831 DPRINTF(E_FATAL,L_MAIN,"Error starting signal handler %s\n",strerror(errno)); 832 } 833 834 835 if(pid_fp) { 836 /* wait to for config.pid to be set by the signal handler */ 837 while(!config.pid) { 838 sleep(1); 839 } 840 841 fprintf(pid_fp,"%d\n",config.pid); 842 fclose(pid_fp); 843 } 844 845 DPRINTF(E_LOG,L_MAIN|L_PL,"Loading playlists\n"); 846 847 if(config.playlist) 848 pl_load(config.playlist); 849 850 if(parseonly) { 851 if(!pl_error) { 852 fprintf(stderr,"Parsed successfully.\n"); 853 pl_dump(); 854 } 855 exit(EXIT_SUCCESS); 856 } 857 858 /* Initialize the database before starting */ 859 DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n"); 860 if(db_init()) { 861 DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_init: %s\n",strerror(errno)); 862 } 863 864 DPRINTF(E_LOG,L_MAIN|L_SCAN,"Starting mp3 scan\n"); 865 if(scan_init(config.mp3dir)) { 866 DPRINTF(E_FATAL,L_MAIN|L_SCAN,"Error scanning MP3 files: %s\n",strerror(errno)); 867 } 868 869 /* start up the web server */ 870 871 ws_config.web_root=config.web_root; 872 ws_config.port=config.port; 873 874 DPRINTF(E_LOG,L_MAIN|L_WS,"Starting web server from %s on port %d\n", 875 config.web_root, config.port); 876 877 server=ws_start(&ws_config); 878 if(!server) { 879 DPRINTF(E_FATAL,L_MAIN|L_WS,"Error staring web server: %s\n",strerror(errno)); 880 } 881 882 ws_registerhandler(server, "^.*$",config_handler,config_auth,1); 883 ws_registerhandler(server, "^/server-info$",daap_handler,NULL,0); 884 ws_registerhandler(server, "^/content-codes$",daap_handler,NULL,0); 885 ws_registerhandler(server,"^/login$",daap_handler,daap_auth,0); 886 ws_registerhandler(server,"^/update$",daap_handler,daap_auth,0); 887 ws_registerhandler(server,"^/databases$",daap_handler,daap_auth,0); 888 ws_registerhandler(server,"^/logout$",daap_handler,NULL,0); 889 ws_registerhandler(server,"^/databases/.*",daap_handler,NULL,0); 890 891#ifndef WITHOUT_MDNS 892 if(config.use_mdns) { /* register services */ 893 DPRINTF(E_LOG,L_MAIN|L_REND,"Registering rendezvous names\n"); 894 rend_register(config.servername,"_daap._tcp",config.port); 895 rend_register(config.servername,"_http._tcp",config.port); 896 } 897#endif 898 899 end_time=time(NULL); 900 901 DPRINTF(E_LOG,L_MAIN,"Scanned %d songs in %d seconds\n",db_get_song_count(), 902 end_time-start_time); 903 904 config.stop=0; 905 906 while(!config.stop) { 907 if((config.rescan_interval) && (rescan_counter > config.rescan_interval)) { 908 if((config.always_scan) || (config_get_session_count())) { 909 config.reload=1; 910 } else { 911 DPRINTF(E_DBG,L_MAIN|L_SCAN|L_DB,"Skipped bground scan... no users\n"); 912 } 913 rescan_counter=0; 914 } 915 916 if(config.reload) { 917 old_song_count = db_get_song_count(); 918 start_time=time(NULL); 919 920// DPRINTF(E_LOG,L_MAIN|L_DB|L_SCAN,"Rescanning database\n"); // J++. noisy... 921 if(scan_init(config.mp3dir)) { 922 DPRINTF(E_LOG,L_MAIN|L_DB|L_SCAN,"Error rescanning... exiting\n"); 923 config.stop=1; 924 } 925 config.reload=0; 926 DPRINTF(E_INF,L_MAIN|L_DB|L_SCAN,"Scanned %d songs (was %d) in %d seconds\n", 927 db_get_song_count(),old_song_count,time(NULL)-start_time); 928 } 929 930 sleep(MAIN_SLEEP_INTERVAL); 931 rescan_counter += MAIN_SLEEP_INTERVAL; 932 } 933 934 DPRINTF(E_LOG,L_MAIN,"Stopping gracefully\n"); 935 936#ifndef WITHOUT_MDNS 937 if(config.use_mdns) { 938 DPRINTF(E_LOG,L_MAIN|L_REND,"Stopping rendezvous daemon\n"); 939 rend_stop(); 940 } 941#endif 942 943 DPRINTF(E_LOG,L_MAIN,"Stopping signal handler\n"); 944 if(!pthread_kill(signal_tid,SIGINT)) { 945 pthread_join(signal_tid,NULL); 946 } 947 948 /* Got to find a cleaner way to stop the web server. 949 * Closing the fd of the socking accepting doesn't necessarily 950 * cause the accept to fail on some libcs. 951 * 952 DPRINTF(E_LOG,L_MAIN|L_WS,"Stopping web server\n"); 953 ws_stop(server); 954 */ 955 956 config_close(); 957 958 DPRINTF(E_LOG,L_MAIN|L_DB,"Closing database\n"); 959 db_deinit(); 960 961#ifdef DEBUG_MEMORY 962 fprintf(stderr,"Leaked memory:\n"); 963 err_leakcheck(); 964#endif 965 966 DPRINTF(E_LOG,L_MAIN,"Done!\n"); 967 968 err_setdest(NULL,LOGDEST_STDERR); 969 970 return EXIT_SUCCESS; 971} 972 973