static void * lastfm(void *arg) { int ret; DPRINTF(E_DBG, L_LASTFM, "Main loop initiating\n"); ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_LASTFM, "Error: DB init failed\n"); pthread_exit(NULL); } event_base_dispatch(evbase_lastfm); if (g_initialized) { DPRINTF(E_LOG, L_LASTFM, "LastFM event loop terminated ahead of time!\n"); g_initialized = 0; } db_perthread_deinit(); DPRINTF(E_DBG, L_LASTFM, "Main loop terminating\n"); pthread_exit(NULL); }
static void * worker(void *arg) { int ret; ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_MAIN, "Error: DB init failed (worker thread)\n"); pthread_exit(NULL); } g_initialized = 1; event_base_dispatch(evbase_worker); if (g_initialized) { DPRINTF(E_LOG, L_MAIN, "Worker event loop terminated ahead of time!\n"); g_initialized = 0; } db_perthread_deinit(); pthread_exit(NULL); }
/* Thread: scan */ static void * filescanner(void *arg) { int ret; ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: DB init failed\n"); pthread_exit(NULL); } ret = db_watch_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n"); pthread_exit(NULL); } ret = db_groups_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear old groups from DB\n"); pthread_exit(NULL); } /* Recompute all songalbumids, in case the SQLite DB got transferred * to a different host; the hash is not portable. * It will also rebuild the groups we just cleared. */ db_files_update_songalbumid(); bulk_scan(); db_hook_post_scan(); if (!scan_exit) { /* Enable inotify */ event_add(&inoev, NULL); event_base_dispatch(evbase_scan); } if (!scan_exit) DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n"); db_perthread_deinit(); pthread_exit(NULL); }
/* Thread: httpd */ static void * httpd(void *arg) { int ret; ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n"); pthread_exit(NULL); } event_base_dispatch(evbase_httpd); if (!httpd_exit) DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n"); db_perthread_deinit(); pthread_exit(NULL); }
static void * library(void *arg) { int ret; #ifdef __linux__ struct sched_param param; /* Lower the priority of the thread so forked-daapd may still respond * during library scan on low power devices. Param must be 0 for the SCHED_BATCH * policy. */ memset(¶m, 0, sizeof(struct sched_param)); ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (ret != 0) { DPRINTF(E_LOG, L_LIB, "Warning: Could not set thread priority to SCHED_BATCH\n"); } #endif ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_LIB, "Error: DB init failed\n"); pthread_exit(NULL); } initscan(); event_base_dispatch(evbase_lib); if (!scan_exit) DPRINTF(E_FATAL, L_LIB, "Scan event loop terminated ahead of time!\n"); db_perthread_deinit(); pthread_exit(NULL); }
static void * cache(void *arg) { int ret; ret = cache_create(); if (ret < 0) { DPRINTF(E_LOG, L_CACHE, "Error: Cache create failed\n"); pthread_exit(NULL); } ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_CACHE, "Error: DB init failed\n"); cache_close(); pthread_exit(NULL); } g_initialized = 1; event_base_dispatch(evbase_cache); if (g_initialized) { DPRINTF(E_LOG, L_CACHE, "cache event loop terminated ahead of time!\n"); g_initialized = 0; } db_perthread_deinit(); cache_close(); pthread_exit(NULL); }
int main(int argc, char **argv) { int option; char *configfile; int background; int mdns_no_rsp; int mdns_no_daap; int loglevel; char *logdomains; char *logfile; char *ffid; char *pidfile; const char *gcry_version; sigset_t sigs; int sigfd; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) struct kevent ke_sigs[4]; #endif int ret; struct option option_map[] = { { "ffid", 1, NULL, 'b' }, { "debug", 1, NULL, 'd' }, { "logdomains", 1, NULL, 'D' }, { "foreground", 0, NULL, 'f' }, { "config", 1, NULL, 'c' }, { "pidfile", 1, NULL, 'P' }, { "version", 0, NULL, 'v' }, { "mdns-no-rsp", 0, NULL, 512 }, { "mdns-no-daap", 0, NULL, 513 }, { NULL, 0, NULL, 0 } }; configfile = CONFFILE; pidfile = PIDFILE; loglevel = -1; logdomains = NULL; logfile = NULL; background = 1; ffid = NULL; mdns_no_rsp = 0; mdns_no_daap = 0; while ((option = getopt_long(argc, argv, "D:d:c:P:fb:v", option_map, NULL)) != -1) { switch (option) { case 512: mdns_no_rsp = 1; break; case 513: mdns_no_daap = 1; break; case 'b': ffid = optarg; break; case 'd': ret = safe_atoi32(optarg, &option); if (ret < 0) fprintf(stderr, "Error: loglevel must be an integer in '-d %s'\n", optarg); else loglevel = option; break; case 'D': logdomains = optarg; break; case 'f': background = 0; break; case 'c': configfile = optarg; break; case 'P': pidfile = optarg; break; case 'v': version(); return EXIT_SUCCESS; break; default: usage(argv[0]); return EXIT_FAILURE; break; } } ret = logger_init(NULL, NULL, (loglevel < 0) ? E_LOG : loglevel); if (ret != 0) { fprintf(stderr, "Could not initialize log facility\n"); return EXIT_FAILURE; } ret = conffile_load(configfile); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "Config file errors; please fix your config\n"); logger_deinit(); return EXIT_FAILURE; } logger_deinit(); /* Reinit log facility with configfile values */ if (loglevel < 0) loglevel = cfg_getint(cfg_getsec(cfg, "general"), "loglevel"); logfile = cfg_getstr(cfg_getsec(cfg, "general"), "logfile"); ret = logger_init(logfile, logdomains, loglevel); if (ret != 0) { fprintf(stderr, "Could not reinitialize log facility with config file settings\n"); conffile_unload(); return EXIT_FAILURE; } /* Set up libevent logging callback */ event_set_log_callback(logger_libevent); DPRINTF(E_LOG, L_MAIN, "Forked Media Server Version %s taking off\n", VERSION); ret = av_lockmgr_register(ffmpeg_lockmgr); if (ret < 0) { DPRINTF(E_FATAL, L_MAIN, "Could not register ffmpeg lock manager callback\n"); ret = EXIT_FAILURE; goto ffmpeg_init_fail; } av_register_all(); #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13) avformat_network_init(); #endif av_log_set_callback(logger_ffmpeg); #ifdef LASTFM /* Initialize libcurl */ curl_global_init(CURL_GLOBAL_DEFAULT); #endif /* Initialize libgcrypt */ gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); gcry_version = gcry_check_version(GCRYPT_VERSION); if (!gcry_version) { DPRINTF(E_FATAL, L_MAIN, "libgcrypt version mismatch\n"); ret = EXIT_FAILURE; goto gcrypt_init_fail; } /* We aren't handling anything sensitive, so give up on secure * memory, which is a scarce system resource. */ gcry_control(GCRYCTL_DISABLE_SECMEM, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); DPRINTF(E_DBG, L_MAIN, "Initialized with gcrypt %s\n", gcry_version); /* Block signals for all threads except the main one */ sigemptyset(&sigs); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGCHLD); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGPIPE); ret = pthread_sigmask(SIG_BLOCK, &sigs, NULL); if (ret != 0) { DPRINTF(E_LOG, L_MAIN, "Error setting signal set\n"); ret = EXIT_FAILURE; goto signal_block_fail; } /* Daemonize and drop privileges */ ret = daemonize(background, pidfile); if (ret < 0) { DPRINTF(E_LOG, L_MAIN, "Could not initialize server\n"); ret = EXIT_FAILURE; goto daemon_fail; } /* Initialize libevent (after forking) */ evbase_main = event_init(); DPRINTF(E_LOG, L_MAIN, "mDNS init\n"); ret = mdns_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "mDNS init failed\n"); ret = EXIT_FAILURE; goto mdns_fail; } /* Initialize the database before starting */ DPRINTF(E_INFO, L_MAIN, "Initializing database\n"); ret = db_init(); if (ret < 0) { DPRINTF(E_FATAL, L_MAIN, "Database init failed\n"); ret = EXIT_FAILURE; goto db_fail; } /* Open a DB connection for the main thread */ ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_FATAL, L_MAIN, "Could not perform perthread DB init for main\n"); ret = EXIT_FAILURE; goto db_fail; } /* Spawn worker thread */ ret = worker_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "Worker thread failed to start\n"); ret = EXIT_FAILURE; goto worker_fail; } /* Spawn cache thread */ ret = cache_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "Cache thread failed to start\n"); ret = EXIT_FAILURE; goto cache_fail; } /* Spawn file scanner thread */ ret = filescanner_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "File scanner thread failed to start\n"); ret = EXIT_FAILURE; goto filescanner_fail; } #ifdef HAVE_SPOTIFY_H /* Spawn Spotify thread */ ret = spotify_init(); if (ret < 0) { DPRINTF(E_INFO, L_MAIN, "Spotify thread not started\n");; } #endif /* Spawn player thread */ ret = player_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "Player thread failed to start\n"); ret = EXIT_FAILURE; goto player_fail; } /* Spawn HTTPd thread */ ret = httpd_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "HTTPd thread failed to start\n"); ret = EXIT_FAILURE; goto httpd_fail; } #ifdef MPD /* Spawn MPD thread */ ret = mpd_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "MPD thread failed to start\n"); ret = EXIT_FAILURE; goto mpd_fail; } #endif /* Start Remote pairing service */ ret = remote_pairing_init(); if (ret != 0) { DPRINTF(E_FATAL, L_MAIN, "Remote pairing service failed to start\n"); ret = EXIT_FAILURE; goto remote_fail; } /* Register mDNS services */ ret = register_services(ffid, mdns_no_rsp, mdns_no_daap); if (ret < 0) { ret = EXIT_FAILURE; goto mdns_reg_fail; } #if defined(__linux__) /* Set up signal fd */ sigfd = signalfd(-1, &sigs, SFD_NONBLOCK | SFD_CLOEXEC); if (sigfd < 0) { DPRINTF(E_FATAL, L_MAIN, "Could not setup signalfd: %s\n", strerror(errno)); ret = EXIT_FAILURE; goto signalfd_fail; } event_set(&sig_event, sigfd, EV_READ, signal_signalfd_cb, NULL); #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) sigfd = kqueue(); if (sigfd < 0) { DPRINTF(E_FATAL, L_MAIN, "Could not setup kqueue: %s\n", strerror(errno)); ret = EXIT_FAILURE; goto signalfd_fail; } EV_SET(&ke_sigs[0], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); EV_SET(&ke_sigs[1], SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); EV_SET(&ke_sigs[2], SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); EV_SET(&ke_sigs[3], SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); ret = kevent(sigfd, ke_sigs, 4, NULL, 0, NULL); if (ret < 0) { DPRINTF(E_FATAL, L_MAIN, "Could not register signal events: %s\n", strerror(errno)); ret = EXIT_FAILURE; goto signalfd_fail; } event_set(&sig_event, sigfd, EV_READ, signal_kqueue_cb, NULL); #endif event_base_set(evbase_main, &sig_event); event_add(&sig_event, NULL); /* Run the loop */ event_base_dispatch(evbase_main); DPRINTF(E_LOG, L_MAIN, "Stopping gracefully\n"); ret = EXIT_SUCCESS; /* * On a clean shutdown, bring mDNS down first to give a chance * to the clients to perform a clean shutdown on their end */ DPRINTF(E_LOG, L_MAIN, "mDNS deinit\n"); mdns_deinit(); signalfd_fail: mdns_reg_fail: DPRINTF(E_LOG, L_MAIN, "Remote pairing deinit\n"); remote_pairing_deinit(); remote_fail: DPRINTF(E_LOG, L_MAIN, "HTTPd deinit\n"); httpd_deinit(); httpd_fail: DPRINTF(E_LOG, L_MAIN, "TCPd deinit\n"); #ifdef MPD DPRINTF(E_LOG, L_MAIN, "MPD deinit\n"); mpd_deinit(); mpd_fail: #endif DPRINTF(E_LOG, L_MAIN, "Player deinit\n"); player_deinit(); player_fail: #ifdef HAVE_SPOTIFY_H DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n"); spotify_deinit(); #endif DPRINTF(E_LOG, L_MAIN, "File scanner deinit\n"); filescanner_deinit(); filescanner_fail: DPRINTF(E_LOG, L_MAIN, "Cache deinit\n"); cache_deinit(); cache_fail: DPRINTF(E_LOG, L_MAIN, "Worker deinit\n"); worker_deinit(); worker_fail: DPRINTF(E_LOG, L_MAIN, "Database deinit\n"); db_perthread_deinit(); db_deinit(); db_fail: if (ret == EXIT_FAILURE) { DPRINTF(E_LOG, L_MAIN, "mDNS deinit\n"); mdns_deinit(); } mdns_fail: daemon_fail: if (background) { ret = seteuid(0); if (ret < 0) DPRINTF(E_LOG, L_MAIN, "seteuid() failed: %s\n", strerror(errno)); else { ret = unlink(pidfile); if (ret < 0) DPRINTF(E_LOG, L_MAIN, "Could not unlink PID file %s: %s\n", pidfile, strerror(errno)); } } signal_block_fail: gcrypt_init_fail: #ifdef LASTFM curl_global_cleanup(); #endif #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13) avformat_network_deinit(); #endif av_lockmgr_register(NULL); ffmpeg_init_fail: DPRINTF(E_LOG, L_MAIN, "Exiting.\n"); conffile_unload(); logger_deinit(); return ret; }
/* Thread: scan */ static void * filescanner(void *arg) { int ret; #if defined(__linux__) struct sched_param param; /* Lower the priority of the thread so forked-daapd may still respond * during file scan on low power devices. Param must be 0 for the SCHED_BATCH * policy. */ memset(¶m, 0, sizeof(struct sched_param)); ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (ret != 0) { DPRINTF(E_LOG, L_SCAN, "Warning: Could not set thread priority to SCHED_BATCH\n"); } #endif ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: DB init failed\n"); pthread_exit(NULL); } ret = db_watch_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n"); pthread_exit(NULL); } ret = db_groups_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear old groups from DB\n"); pthread_exit(NULL); } /* Recompute all songartistids and songalbumids, in case the SQLite DB got transferred * to a different host; the hash is not portable. * It will also rebuild the groups we just cleared. */ db_files_update_songartistid(); db_files_update_songalbumid(); if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable")) bulk_scan(F_SCAN_BULK | F_SCAN_FAST); else bulk_scan(F_SCAN_BULK); if (!scan_exit) { #ifdef HAVE_SPOTIFY_H spotify_login(NULL); #endif /* Enable inotify */ event_add(&inoev, NULL); event_base_dispatch(evbase_scan); } if (!scan_exit) DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n"); db_perthread_deinit(); pthread_exit(NULL); }
/* Thread: scan */ static void * filescanner(void *arg) { int clear_queue_on_stop_disabled; int ret; #ifdef __linux__ struct sched_param param; /* Lower the priority of the thread so forked-daapd may still respond * during file scan on low power devices. Param must be 0 for the SCHED_BATCH * policy. */ memset(¶m, 0, sizeof(struct sched_param)); ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (ret != 0) { DPRINTF(E_LOG, L_SCAN, "Warning: Could not set thread priority to SCHED_BATCH\n"); } #endif ret = db_perthread_init(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: DB init failed\n"); pthread_exit(NULL); } ret = db_watch_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n"); pthread_exit(NULL); } // Only clear the queue if enabled (default) in config clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable"); if (!clear_queue_on_stop_disabled) { ret = db_queue_clear(); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error: could not clear queue from DB\n"); pthread_exit(NULL); } } if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable")) bulk_scan(F_SCAN_BULK | F_SCAN_FAST); else bulk_scan(F_SCAN_BULK); if (!scan_exit) { #ifdef HAVE_SPOTIFY_H spotify_login(NULL); #endif /* Enable inotify */ event_add(inoev, NULL); event_base_dispatch(evbase_scan); } if (!scan_exit) DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n"); db_perthread_deinit(); pthread_exit(NULL); }